istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/externalcontrolplane/externalcontrolplane.go (about) 1 // Copyright Istio Authors 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 externalcontrolplane 16 17 import ( 18 "fmt" 19 "net" 20 "net/url" 21 "strings" 22 23 v1 "k8s.io/api/admissionregistration/v1" 24 25 "istio.io/istio/pkg/config" 26 "istio.io/istio/pkg/config/analysis" 27 "istio.io/istio/pkg/config/analysis/msg" 28 "istio.io/istio/pkg/config/resource" 29 "istio.io/istio/pkg/config/schema/gvk" 30 ) 31 32 type ExternalControlPlaneAnalyzer struct{} 33 34 // Compile-time check that this Analyzer correctly implements the interface 35 var _ analysis.Analyzer = &ExternalControlPlaneAnalyzer{} 36 37 // Metadata implements Analyzer 38 func (s *ExternalControlPlaneAnalyzer) Metadata() analysis.Metadata { 39 return analysis.Metadata{ 40 Name: "externalcontrolplane.ExternalControlPlaneAnalyzer", 41 Description: "Checks that the remote IstioOperator resources reference an external control plane", 42 Inputs: []config.GroupVersionKind{ 43 gvk.ValidatingWebhookConfiguration, 44 gvk.MutatingWebhookConfiguration, 45 }, 46 } 47 } 48 49 const ( 50 defaultIstioValidatingWebhookName = "istiod-default-validator" 51 istioValidatingWebhookNamePrefix = "istio-validator" 52 istioMutatingWebhookNamePrefix = "istio-sidecar-injector" 53 ) 54 55 // Analyze implements Analyzer 56 func (s *ExternalControlPlaneAnalyzer) Analyze(c analysis.Context) { 57 reportWebhookURL := func(r *resource.Instance, hName string, clientConf v1.WebhookClientConfig) { 58 // If defined, it means that an external istiod has been adopted 59 if clientConf.URL != nil { 60 result, err := lintWebhookURL(*clientConf.URL) 61 if err != nil { 62 c.Report(gvk.ValidatingWebhookConfiguration, msg.NewInvalidExternalControlPlaneConfig(r, *clientConf.URL, hName, err.Error())) 63 return 64 } 65 if result.isIP() { 66 c.Report(gvk.ValidatingWebhookConfiguration, msg.NewExternalControlPlaneAddressIsNotAHostname(r, *clientConf.URL, hName)) 67 } 68 } else if clientConf.Service == nil { 69 c.Report(gvk.ValidatingWebhookConfiguration, msg.NewInvalidExternalControlPlaneConfig(r, "", hName, "is blank")) 70 } 71 } 72 73 c.ForEach(gvk.ValidatingWebhookConfiguration, func(resource *resource.Instance) bool { 74 webhookConfig := resource.Message.(*v1.ValidatingWebhookConfiguration) 75 76 // 1. ValidatingWebhookConfiguration: istio-validator or istiod-default-validator(default) 77 // istio-validator{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}-{{ .Values.global.istioNamespace }} 78 if webhookConfig.GetName() != "" && 79 (webhookConfig.Name == defaultIstioValidatingWebhookName || 80 strings.HasPrefix(webhookConfig.Name, istioValidatingWebhookNamePrefix)) { 81 for _, hook := range webhookConfig.Webhooks { 82 reportWebhookURL(resource, hook.Name, hook.ClientConfig) 83 } 84 } 85 86 return true 87 }) 88 89 c.ForEach(gvk.MutatingWebhookConfiguration, func(resource *resource.Instance) bool { 90 webhookConfig := resource.Message.(*v1.MutatingWebhookConfiguration) 91 92 // 2. MutatingWebhookConfiguration: istio-sidecar-injector 93 // {{- if eq .Release.Namespace "istio-system"}} 94 // name: istio-sidecar-injector{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }} 95 // {{- else }} 96 // name: istio-sidecar-injector{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}-{{ .Release.Namespace }} 97 // {{- end }} 98 if strings.HasPrefix(webhookConfig.Name, istioMutatingWebhookNamePrefix) { 99 for _, hook := range webhookConfig.Webhooks { 100 reportWebhookURL(resource, hook.Name, hook.ClientConfig) 101 } 102 } 103 104 return true 105 }) 106 } 107 108 type webhookURLResult struct { 109 ip net.IP 110 hostName string 111 } 112 113 func (r *webhookURLResult) isIP() bool { 114 if r == nil { 115 return false 116 } 117 return r.ip != nil 118 } 119 120 func lintWebhookURL(webhookURL string) (result *webhookURLResult, err error) { 121 result = &webhookURLResult{} 122 parsedWebhookURL, err := url.Parse(webhookURL) 123 if err != nil { 124 return result, fmt.Errorf("was provided in an invalid format") 125 } 126 127 parsedHostname := parsedWebhookURL.Hostname() 128 if ip := net.ParseIP(parsedHostname); ip != nil { 129 result.ip = ip 130 return result, nil 131 } 132 133 result.hostName = parsedHostname 134 135 return result, nil 136 }