istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/precheck/precheck.go (about)

     1  // Copyright © 2021 NAME HERE <EMAIL ADDRESS>
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package precheck
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"github.com/fatih/color"
    25  	"github.com/spf13/cobra"
    26  	"gopkg.in/yaml.v2"
    27  	authorizationapi "k8s.io/api/authorization/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	crd "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    30  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  
    33  	"istio.io/api/label"
    34  	networking "istio.io/api/networking/v1alpha3"
    35  	"istio.io/istio/istioctl/pkg/cli"
    36  	"istio.io/istio/istioctl/pkg/clioptions"
    37  	"istio.io/istio/istioctl/pkg/install/k8sversion"
    38  	"istio.io/istio/istioctl/pkg/util/formatting"
    39  	istiocluster "istio.io/istio/pkg/cluster"
    40  	"istio.io/istio/pkg/config"
    41  	"istio.io/istio/pkg/config/analysis"
    42  	"istio.io/istio/pkg/config/analysis/analyzers/maturity"
    43  	"istio.io/istio/pkg/config/analysis/diag"
    44  	legacykube "istio.io/istio/pkg/config/analysis/legacy/source/kube"
    45  	"istio.io/istio/pkg/config/analysis/local"
    46  	"istio.io/istio/pkg/config/analysis/msg"
    47  	"istio.io/istio/pkg/config/host"
    48  	"istio.io/istio/pkg/config/resource"
    49  	"istio.io/istio/pkg/config/schema/gvk"
    50  	"istio.io/istio/pkg/config/schema/kubetypes"
    51  	"istio.io/istio/pkg/kube"
    52  	"istio.io/istio/pkg/kube/controllers"
    53  	"istio.io/istio/pkg/url"
    54  	"istio.io/istio/pkg/util/sets"
    55  )
    56  
    57  func Cmd(ctx cli.Context) *cobra.Command {
    58  	var opts clioptions.ControlPlaneOptions
    59  	var skipControlPlane bool
    60  	outputThreshold := formatting.MessageThreshold{Level: diag.Warning}
    61  	var msgOutputFormat string
    62  	var fromCompatibilityVersion string
    63  	// cmd represents the upgradeCheck command
    64  	cmd := &cobra.Command{
    65  		Use:   "precheck",
    66  		Short: "Check whether Istio can safely be installed or upgraded",
    67  		Long:  `precheck inspects a Kubernetes cluster for Istio install and upgrade requirements.`,
    68  		Example: `  # Verify that Istio can be installed or upgraded
    69    istioctl x precheck
    70  
    71    # Check only a single namespace
    72    istioctl x precheck --namespace default
    73  
    74    # Check for behavioral changes since a specific version
    75    istioctl x precheck --from-version 1.10`,
    76  		RunE: func(cmd *cobra.Command, args []string) (err error) {
    77  			msgs := diag.Messages{}
    78  			if !skipControlPlane {
    79  				msgs, err = checkControlPlane(ctx)
    80  				if err != nil {
    81  					return err
    82  				}
    83  			}
    84  
    85  			if fromCompatibilityVersion != "" {
    86  				m, err := checkFromVersion(ctx, opts.Revision, fromCompatibilityVersion)
    87  				if err != nil {
    88  					return err
    89  				}
    90  				msgs = append(msgs, m...)
    91  			}
    92  
    93  			// Print all the messages to stdout in the specified format
    94  			msgs = msgs.SortedDedupedCopy()
    95  			outputMsgs := diag.Messages{}
    96  			for _, m := range msgs {
    97  				if m.Type.Level().IsWorseThanOrEqualTo(outputThreshold.Level) {
    98  					outputMsgs = append(outputMsgs, m)
    99  				}
   100  			}
   101  			output, err := formatting.Print(outputMsgs, msgOutputFormat, true)
   102  			if err != nil {
   103  				return err
   104  			}
   105  
   106  			if len(outputMsgs) == 0 {
   107  				fmt.Fprintf(cmd.ErrOrStderr(), color.New(color.FgGreen).Sprint("✔")+" No issues found when checking the cluster. Istio is safe to install or upgrade!\n"+
   108  					"  To get started, check out https://istio.io/latest/docs/setup/getting-started/\n")
   109  			} else {
   110  				fmt.Fprintln(cmd.OutOrStdout(), output)
   111  			}
   112  			for _, m := range msgs {
   113  				if m.Type.Level().IsWorseThanOrEqualTo(diag.Warning) {
   114  					e := fmt.Sprintf(`Issues found when checking the cluster. Istio may not be safe to install or upgrade.
   115  See %s for more information about causes and resolutions.`, url.ConfigAnalysis)
   116  					return errors.New(e)
   117  				}
   118  			}
   119  			return nil
   120  		},
   121  	}
   122  	cmd.PersistentFlags().BoolVar(&skipControlPlane, "skip-controlplane", false, "skip checking the control plane")
   123  	cmd.PersistentFlags().Var(&outputThreshold, "output-threshold",
   124  		fmt.Sprintf("The severity level of precheck at which to display messages. Valid values: %v", diag.GetAllLevelStrings()))
   125  	cmd.PersistentFlags().StringVarP(&msgOutputFormat, "output", "o", formatting.LogFormat,
   126  		fmt.Sprintf("Output format: one of %v", formatting.MsgOutputFormatKeys))
   127  	cmd.PersistentFlags().StringVarP(&fromCompatibilityVersion, "from-version", "f", "",
   128  		"check changes since the provided version")
   129  	opts.AttachControlPlaneFlags(cmd)
   130  	return cmd
   131  }
   132  
   133  func checkFromVersion(ctx cli.Context, revision, version string) (diag.Messages, error) {
   134  	cli, err := ctx.CLIClientWithRevision(revision)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	major, minors, ok := strings.Cut(version, ".")
   139  	if !ok {
   140  		return nil, fmt.Errorf("invalid version %v, expected format like '1.0'", version)
   141  	}
   142  	if major != "1" {
   143  		return nil, fmt.Errorf("expected major version 1, got %v", version)
   144  	}
   145  	minor, err := strconv.Atoi(minors)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("minor version is not a number: %v", minors)
   148  	}
   149  
   150  	var messages diag.Messages = make([]diag.Message, 0)
   151  	if minor <= 21 {
   152  		// ENHANCED_RESOURCE_SCOPING
   153  		if err := checkPilot(cli, ctx.IstioNamespace(), &messages); err != nil {
   154  			return nil, err
   155  		}
   156  	}
   157  	if minor <= 20 {
   158  		// VERIFY_CERTIFICATE_AT_CLIENT and ENABLE_AUTO_SNI
   159  		if err := checkDestinationRuleTLS(cli, &messages); err != nil {
   160  			return nil, err
   161  		}
   162  		// ENABLE_EXTERNAL_NAME_ALIAS
   163  		if err := checkExternalNameAlias(cli, &messages); err != nil {
   164  			return nil, err
   165  		}
   166  		// PERSIST_OLDEST_FIRST_HEURISTIC_FOR_VIRTUAL_SERVICE_HOST_MATCHING
   167  		if err := checkVirtualServiceHostMatching(cli, &messages); err != nil {
   168  			return nil, err
   169  		}
   170  	}
   171  	if minor <= 21 {
   172  		if err := checkPassthroughTargetPorts(cli, &messages); err != nil {
   173  			return nil, err
   174  		}
   175  		if err := checkTracing(cli, &messages); err != nil {
   176  			return nil, err
   177  		}
   178  	}
   179  	return messages, nil
   180  }
   181  
   182  func checkTracing(cli kube.CLIClient, messages *diag.Messages) error {
   183  	// In 1.22, we remove the default tracing config which points to zipkin.istio-system
   184  	// This has no effect for users, unless they have this service.
   185  	svc, err := cli.Kube().CoreV1().Services("istio-system").Get(context.Background(), "zipkin", metav1.GetOptions{})
   186  	if err != nil && !kerrors.IsNotFound(err) {
   187  		return err
   188  	}
   189  	if err != nil {
   190  		// not found
   191  		return nil
   192  	}
   193  	// found
   194  	res := ObjectToInstance(svc)
   195  	messages.Add(msg.NewUpdateIncompatibility(res,
   196  		"meshConfig.defaultConfig.tracer", "1.21",
   197  		"tracing is no longer by default enabled to send to 'zipkin.istio-system.svc'; "+
   198  			"follow https://istio.io/latest/docs/tasks/observability/distributed-tracing/telemetry-api/",
   199  		"1.21"))
   200  	return nil
   201  }
   202  
   203  func checkPassthroughTargetPorts(cli kube.CLIClient, messages *diag.Messages) error {
   204  	ses, err := cli.Istio().NetworkingV1alpha3().ServiceEntries(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{})
   205  	if err != nil {
   206  		return err
   207  	}
   208  	for _, se := range ses.Items {
   209  		if se.Spec.Resolution != networking.ServiceEntry_NONE {
   210  			continue
   211  		}
   212  		changed := false
   213  		for _, p := range se.Spec.Ports {
   214  			if p.TargetPort != 0 && p.Number != p.TargetPort {
   215  				changed = true
   216  			}
   217  		}
   218  		if changed {
   219  			res := ObjectToInstance(se)
   220  			messages.Add(msg.NewUpdateIncompatibility(res,
   221  				"ENABLE_RESOLUTION_NONE_TARGET_PORT", "1.21",
   222  				"ServiceEntry with resolution NONE and a targetPort set previously did nothing but now is respected", "1.21"))
   223  		}
   224  	}
   225  	return nil
   226  }
   227  
   228  func checkExternalNameAlias(cli kube.CLIClient, messages *diag.Messages) error {
   229  	svcs, err := cli.Kube().CoreV1().Services(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{})
   230  	if err != nil {
   231  		return err
   232  	}
   233  	for _, svc := range svcs.Items {
   234  		if svc.Spec.Type != corev1.ServiceTypeExternalName {
   235  			continue
   236  		}
   237  		res := ObjectToInstance(&svc)
   238  		messages.Add(msg.NewUpdateIncompatibility(res,
   239  			"ENABLE_EXTERNAL_NAME_ALIAS", "1.20",
   240  			"ExternalName services now behavior differently; consult upgrade notes for more information", "1.20"))
   241  
   242  	}
   243  	return nil
   244  }
   245  
   246  func checkPilot(cli kube.CLIClient, namespace string, messages *diag.Messages) error {
   247  	deployments, err := cli.Kube().AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{
   248  		LabelSelector: "app=istiod",
   249  	})
   250  	if err != nil {
   251  		return err
   252  	}
   253  	for _, deployment := range deployments.Items {
   254  		scopingImpacted := false
   255  
   256  		// Obtain configmap to verify if affected features are used
   257  		configMapName := "istio"
   258  		if rev := deployment.Labels[label.IoIstioRev.Name]; rev != "default" {
   259  			configMapName += fmt.Sprintf("-%s", rev)
   260  		}
   261  		configMap, err := cli.Kube().CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapName, metav1.GetOptions{})
   262  		if err != nil {
   263  			fmt.Printf("Error getting configmap %s: %v\n", configMapName, err)
   264  		}
   265  		meshData := make(map[string]interface{})
   266  		if data, exists := configMap.Data["mesh"]; exists {
   267  			if err := yaml.Unmarshal([]byte(data), &meshData); err != nil {
   268  				fmt.Printf("Error parsing meshConfig: %v\n", err)
   269  				return err
   270  			}
   271  		}
   272  		if scopingImpacted = meshData["discoverySelectors"] != nil; !scopingImpacted {
   273  			continue
   274  		}
   275  		// Check if mitigation is already in place
   276  		for _, container := range deployment.Spec.Template.Spec.Containers {
   277  			if container.Name == "discovery" {
   278  				for _, envVar := range container.Env {
   279  					if envVar.Name == "ENHANCED_RESOURCE_SCOPING" && envVar.Value == "true" {
   280  						scopingImpacted = false
   281  						break
   282  					}
   283  				}
   284  			}
   285  		}
   286  		if scopingImpacted {
   287  			res := &resource.Instance{
   288  				Origin: &legacykube.Origin{
   289  					Type: config.GroupVersionKind(deployment.GroupVersionKind()),
   290  					FullName: resource.FullName{
   291  						Namespace: resource.Namespace(deployment.GetNamespace()),
   292  						Name:      resource.LocalName(deployment.GetName()),
   293  					},
   294  					ResourceVersion: resource.Version(deployment.GetResourceVersion()),
   295  					Ref:             nil,
   296  					FieldsMap:       nil,
   297  				},
   298  			}
   299  			messages.Add(msg.NewUpdateIncompatibility(res,
   300  				"ENHANCED_RESOURCE_SCOPING", "1.22",
   301  				"previously, the enhanced scoping of custom resources was disabled by default; now it will be enabled by default", "1.21"))
   302  		}
   303  	}
   304  	return nil
   305  }
   306  
   307  func checkDestinationRuleTLS(cli kube.CLIClient, messages *diag.Messages) error {
   308  	drs, err := cli.Istio().NetworkingV1alpha3().DestinationRules(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{})
   309  	if err != nil {
   310  		return err
   311  	}
   312  	checkVerify := func(tls *networking.ClientTLSSettings) bool {
   313  		if tls == nil {
   314  			return false
   315  		}
   316  		if tls.Mode == networking.ClientTLSSettings_DISABLE || tls.Mode == networking.ClientTLSSettings_ISTIO_MUTUAL {
   317  			return false
   318  		}
   319  		return tls.CaCertificates == "" && tls.CredentialName == "" && !tls.InsecureSkipVerify.GetValue()
   320  	}
   321  	checkSNI := func(tls *networking.ClientTLSSettings) bool {
   322  		if tls == nil {
   323  			return false
   324  		}
   325  		if tls.Mode == networking.ClientTLSSettings_DISABLE || tls.Mode == networking.ClientTLSSettings_ISTIO_MUTUAL {
   326  			return false
   327  		}
   328  		return tls.Sni == ""
   329  	}
   330  	for _, dr := range drs.Items {
   331  		verificationImpacted := false
   332  		sniImpacted := false
   333  		verificationImpacted = verificationImpacted || checkVerify(dr.Spec.GetTrafficPolicy().GetTls())
   334  		sniImpacted = sniImpacted || checkSNI(dr.Spec.GetTrafficPolicy().GetTls())
   335  		for _, pl := range dr.Spec.GetTrafficPolicy().GetPortLevelSettings() {
   336  			verificationImpacted = verificationImpacted || checkVerify(pl.GetTls())
   337  			sniImpacted = sniImpacted || checkSNI(pl.GetTls())
   338  		}
   339  		for _, ss := range dr.Spec.Subsets {
   340  			verificationImpacted = verificationImpacted || checkVerify(ss.GetTrafficPolicy().GetTls())
   341  			sniImpacted = sniImpacted || checkSNI(ss.GetTrafficPolicy().GetTls())
   342  			for _, pl := range ss.GetTrafficPolicy().GetPortLevelSettings() {
   343  				verificationImpacted = verificationImpacted || checkVerify(pl.GetTls())
   344  				sniImpacted = sniImpacted || checkSNI(pl.GetTls())
   345  			}
   346  		}
   347  		if verificationImpacted {
   348  			res := ObjectToInstance(dr)
   349  			messages.Add(msg.NewUpdateIncompatibility(res,
   350  				"VERIFY_CERTIFICATE_AT_CLIENT", "1.20",
   351  				"previously, TLS verification was skipped. Set `insecureSkipVerify` if this behavior is desired", "1.20"))
   352  		}
   353  		if sniImpacted {
   354  			res := ObjectToInstance(dr)
   355  			messages.Add(msg.NewUpdateIncompatibility(res,
   356  				"ENABLE_AUTO_SNI", "1.20",
   357  				"previously, no SNI would be set; now it will be automatically set", "1.20"))
   358  		}
   359  	}
   360  	return nil
   361  }
   362  
   363  func checkVirtualServiceHostMatching(cli kube.CLIClient, messages *diag.Messages) error {
   364  	virtualServices, err := cli.Istio().NetworkingV1alpha3().VirtualServices(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{})
   365  	if err != nil {
   366  		return err
   367  	}
   368  	for _, vs := range virtualServices.Items {
   369  		for _, hostname := range vs.Spec.Hosts {
   370  			if host.Name(hostname).IsWildCarded() {
   371  				res := ObjectToInstance(vs)
   372  				messages.Add(msg.NewUpdateIncompatibility(res,
   373  					"PERSIST_OLDEST_FIRST_HEURISTIC_FOR_VIRTUAL_SERVICE_HOST_MATCHING", "1.20",
   374  					"previously, VirtualServices with overlapping wildcard hosts would have the oldest "+
   375  						"VirtualService take precedence. Now, the most specific VirtualService will win", "1.20"),
   376  				)
   377  				continue
   378  			}
   379  		}
   380  	}
   381  	return nil
   382  }
   383  
   384  func ObjectToInstance(c controllers.Object) *resource.Instance {
   385  	return &resource.Instance{
   386  		Origin: &legacykube.Origin{
   387  			Type: kubetypes.GvkFromObject(c),
   388  			FullName: resource.FullName{
   389  				Namespace: resource.Namespace(c.GetNamespace()),
   390  				Name:      resource.LocalName(c.GetName()),
   391  			},
   392  			ResourceVersion: resource.Version(c.GetResourceVersion()),
   393  			Ref:             nil,
   394  			FieldsMap:       nil,
   395  		},
   396  	}
   397  }
   398  
   399  func checkControlPlane(ctx cli.Context) (diag.Messages, error) {
   400  	cli, err := ctx.CLIClient()
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	msgs := diag.Messages{}
   405  
   406  	m, err := checkServerVersion(cli)
   407  	if err != nil {
   408  		return nil, err
   409  	}
   410  	msgs = append(msgs, m...)
   411  
   412  	msgs = append(msgs, checkInstallPermissions(cli, ctx.IstioNamespace())...)
   413  	gwMsg, err := checkGatewayAPIs(cli)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  	msgs = append(msgs, gwMsg...)
   418  
   419  	// TODO: add more checks
   420  
   421  	sa := local.NewSourceAnalyzer(
   422  		analysis.Combine("upgrade precheck", &maturity.AlphaAnalyzer{}),
   423  		resource.Namespace(ctx.Namespace()),
   424  		resource.Namespace(ctx.IstioNamespace()),
   425  		nil,
   426  	)
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  	sa.AddRunningKubeSource(cli)
   431  	cancel := make(chan struct{})
   432  	result, err := sa.Analyze(cancel)
   433  	if err != nil {
   434  		return nil, err
   435  	}
   436  	if result.Messages != nil {
   437  		msgs = append(msgs, result.Messages...)
   438  	}
   439  
   440  	return msgs, nil
   441  }
   442  
   443  // Checks that if the user has gateway APIs, they are the minimum version.
   444  // It is ok to not have them, but they must be at least v1beta1 if they do.
   445  func checkGatewayAPIs(cli kube.CLIClient) (diag.Messages, error) {
   446  	msgs := diag.Messages{}
   447  	res, err := cli.Ext().ApiextensionsV1().CustomResourceDefinitions().List(context.Background(), metav1.ListOptions{})
   448  	if err != nil {
   449  		return nil, err
   450  	}
   451  
   452  	betaKinds := sets.New(gvk.KubernetesGateway.Kind, gvk.GatewayClass.Kind, gvk.HTTPRoute.Kind, gvk.ReferenceGrant.Kind)
   453  	for _, r := range res.Items {
   454  		if r.Spec.Group != gvk.KubernetesGateway.Group {
   455  			continue
   456  		}
   457  		if !betaKinds.Contains(r.Spec.Names.Kind) {
   458  			continue
   459  		}
   460  
   461  		versions := extractCRDVersions(&r)
   462  		has := "none"
   463  		if len(versions) > 0 {
   464  			has = strings.Join(sets.SortedList(versions), ",")
   465  		}
   466  		if !versions.Contains(gvk.KubernetesGateway.Version) {
   467  			origin := legacykube.Origin{
   468  				Type: gvk.CustomResourceDefinition,
   469  				FullName: resource.FullName{
   470  					Namespace: resource.Namespace(r.Namespace),
   471  					Name:      resource.LocalName(r.Name),
   472  				},
   473  				ResourceVersion: resource.Version(r.ResourceVersion),
   474  			}
   475  			r := &resource.Instance{
   476  				Origin: &origin,
   477  			}
   478  			msgs.Add(msg.NewUnsupportedGatewayAPIVersion(r, has, gvk.KubernetesGateway.Version))
   479  		}
   480  	}
   481  	return msgs, nil
   482  }
   483  
   484  func extractCRDVersions(r *crd.CustomResourceDefinition) sets.String {
   485  	res := sets.New[string]()
   486  	for _, v := range r.Spec.Versions {
   487  		if v.Served {
   488  			res.Insert(v.Name)
   489  		}
   490  	}
   491  	return res
   492  }
   493  
   494  func checkInstallPermissions(cli kube.CLIClient, istioNamespace string) diag.Messages {
   495  	Resources := []struct {
   496  		namespace string
   497  		group     string
   498  		version   string
   499  		resource  string
   500  	}{
   501  		{
   502  			version:  "v1",
   503  			resource: "namespaces",
   504  		},
   505  		{
   506  			group:    "rbac.authorization.k8s.io",
   507  			version:  "v1",
   508  			resource: "clusterroles",
   509  		},
   510  		{
   511  			group:    "rbac.authorization.k8s.io",
   512  			version:  "v1",
   513  			resource: "clusterrolebindings",
   514  		},
   515  		{
   516  			group:    "apiextensions.k8s.io",
   517  			version:  "v1",
   518  			resource: "customresourcedefinitions",
   519  		},
   520  		{
   521  			namespace: istioNamespace,
   522  			group:     "rbac.authorization.k8s.io",
   523  			version:   "v1",
   524  			resource:  "roles",
   525  		},
   526  		{
   527  			namespace: istioNamespace,
   528  			version:   "v1",
   529  			resource:  "serviceaccounts",
   530  		},
   531  		{
   532  			namespace: istioNamespace,
   533  			version:   "v1",
   534  			resource:  "services",
   535  		},
   536  		{
   537  			namespace: istioNamespace,
   538  			group:     "apps",
   539  			version:   "v1",
   540  			resource:  "deployments",
   541  		},
   542  		{
   543  			namespace: istioNamespace,
   544  			version:   "v1",
   545  			resource:  "configmaps",
   546  		},
   547  		{
   548  			group:    "admissionregistration.k8s.io",
   549  			version:  "v1",
   550  			resource: "mutatingwebhookconfigurations",
   551  		},
   552  		{
   553  			group:    "admissionregistration.k8s.io",
   554  			version:  "v1",
   555  			resource: "validatingwebhookconfigurations",
   556  		},
   557  	}
   558  	msgs := diag.Messages{}
   559  	for _, r := range Resources {
   560  		err := checkCanCreateResources(cli, r.namespace, r.group, r.version, r.resource)
   561  		if err != nil {
   562  			msgs.Add(msg.NewInsufficientPermissions(&resource.Instance{Origin: clusterOrigin{}}, r.resource, err.Error()))
   563  		}
   564  	}
   565  	return msgs
   566  }
   567  
   568  func checkCanCreateResources(c kube.CLIClient, namespace, group, version, resource string) error {
   569  	s := &authorizationapi.SelfSubjectAccessReview{
   570  		Spec: authorizationapi.SelfSubjectAccessReviewSpec{
   571  			ResourceAttributes: &authorizationapi.ResourceAttributes{
   572  				Namespace: namespace,
   573  				Verb:      "create",
   574  				Group:     group,
   575  				Version:   version,
   576  				Resource:  resource,
   577  			},
   578  		},
   579  	}
   580  
   581  	response, err := c.Kube().AuthorizationV1().SelfSubjectAccessReviews().Create(context.Background(), s, metav1.CreateOptions{})
   582  	if err != nil {
   583  		return err
   584  	}
   585  
   586  	if !response.Status.Allowed {
   587  		if len(response.Status.Reason) > 0 {
   588  			return errors.New(response.Status.Reason)
   589  		}
   590  		return errors.New("permission denied")
   591  	}
   592  	return nil
   593  }
   594  
   595  func checkServerVersion(cli kube.CLIClient) (diag.Messages, error) {
   596  	v, err := cli.GetKubernetesVersion()
   597  	if err != nil {
   598  		return nil, fmt.Errorf("failed to get the Kubernetes version: %v", err)
   599  	}
   600  	compatible, err := k8sversion.CheckKubernetesVersion(v)
   601  	if err != nil {
   602  		return nil, err
   603  	}
   604  	if !compatible {
   605  		return []diag.Message{
   606  			msg.NewUnsupportedKubernetesVersion(&resource.Instance{Origin: clusterOrigin{}}, v.String(), fmt.Sprintf("1.%d", k8sversion.MinK8SVersion)),
   607  		}, nil
   608  	}
   609  	return nil, nil
   610  }
   611  
   612  // clusterOrigin defines an Origin that refers to the cluster
   613  type clusterOrigin struct{}
   614  
   615  func (o clusterOrigin) ClusterName() istiocluster.ID {
   616  	return "Cluster"
   617  }
   618  
   619  func (o clusterOrigin) String() string {
   620  	return ""
   621  }
   622  
   623  func (o clusterOrigin) FriendlyName() string {
   624  	return "Cluster"
   625  }
   626  
   627  func (o clusterOrigin) Comparator() string {
   628  	return o.FriendlyName()
   629  }
   630  
   631  func (o clusterOrigin) Namespace() resource.Namespace {
   632  	return ""
   633  }
   634  
   635  func (o clusterOrigin) Reference() resource.Reference {
   636  	return nil
   637  }
   638  
   639  func (o clusterOrigin) FieldMap() map[string]int {
   640  	return make(map[string]int)
   641  }