istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/injection/image-auto.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 injection 16 17 import ( 18 "strings" 19 20 admitv1 "k8s.io/api/admissionregistration/v1" 21 appsv1 "k8s.io/api/apps/v1" 22 v1 "k8s.io/api/core/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 klabels "k8s.io/apimachinery/pkg/labels" 25 26 "istio.io/istio/pkg/config" 27 "istio.io/istio/pkg/config/analysis" 28 "istio.io/istio/pkg/config/analysis/msg" 29 "istio.io/istio/pkg/config/resource" 30 "istio.io/istio/pkg/config/schema/gvk" 31 ) 32 33 // ImageAutoAnalyzer reports an error if Pods and Deployments with `image: auto` are not going to be injected. 34 type ImageAutoAnalyzer struct{} 35 36 var _ analysis.Analyzer = &ImageAutoAnalyzer{} 37 38 const ( 39 istioProxyContainerName = "istio-proxy" 40 manualInjectionImage = "auto" 41 ) 42 43 // Metadata implements Analyzer. 44 func (a *ImageAutoAnalyzer) Metadata() analysis.Metadata { 45 return analysis.Metadata{ 46 Name: "injection.ImageAutoAnalyzer", 47 Description: "Makes sure that Pods and Deployments with `image: auto` are going to be injected", 48 Inputs: []config.GroupVersionKind{ 49 gvk.Namespace, 50 gvk.Pod, 51 gvk.Deployment, 52 gvk.MutatingWebhookConfiguration, 53 }, 54 } 55 } 56 57 // Analyze implements Analyzer. 58 func (a *ImageAutoAnalyzer) Analyze(c analysis.Context) { 59 var istioWebhooks []admitv1.MutatingWebhook 60 c.ForEach(gvk.MutatingWebhookConfiguration, func(resource *resource.Instance) bool { 61 mwhc := resource.Message.(*admitv1.MutatingWebhookConfiguration) 62 for _, wh := range mwhc.Webhooks { 63 if strings.HasSuffix(wh.Name, "istio.io") { 64 istioWebhooks = append(istioWebhooks, wh) 65 } 66 } 67 return true 68 }) 69 c.ForEach(gvk.Pod, func(resource *resource.Instance) bool { 70 p := resource.Message.(*v1.PodSpec) 71 // If a pod has `image: auto` it is broken whether the webhooks match or not 72 if !hasAutoImage(p) { 73 return true 74 } 75 m := msg.NewImageAutoWithoutInjectionError(resource, "Pod", resource.Metadata.FullName.Name.String()) 76 c.Report(gvk.Pod, m) 77 return true 78 }) 79 c.ForEach(gvk.Deployment, func(resource *resource.Instance) bool { 80 d := resource.Message.(*appsv1.DeploymentSpec) 81 if !hasAutoImage(&d.Template.Spec) { 82 return true 83 } 84 nsLabels := getNamespaceLabels(c, resource.Metadata.FullName.Namespace.String()) 85 if !matchesWebhooks(nsLabels, d.Template.Labels, istioWebhooks) { 86 m := msg.NewImageAutoWithoutInjectionWarning(resource, "Deployment", resource.Metadata.FullName.Name.String()) 87 c.Report(gvk.Deployment, m) 88 } 89 return true 90 }) 91 } 92 93 func hasAutoImage(spec *v1.PodSpec) bool { 94 for _, c := range spec.Containers { 95 if c.Name == istioProxyContainerName && c.Image == manualInjectionImage { 96 return true 97 } 98 } 99 return false 100 } 101 102 func getNamespaceLabels(c analysis.Context, nsName string) map[string]string { 103 if nsName == "" { 104 nsName = "default" 105 } 106 ns := c.Find(gvk.Namespace, resource.NewFullName("", resource.LocalName(nsName))) 107 if ns == nil { 108 return nil 109 } 110 return ns.Metadata.Labels 111 } 112 113 func matchesWebhooks(nsLabels, podLabels map[string]string, istioWebhooks []admitv1.MutatingWebhook) bool { 114 for _, w := range istioWebhooks { 115 if selectorMatches(w.NamespaceSelector, nsLabels) && selectorMatches(w.ObjectSelector, podLabels) { 116 return true 117 } 118 } 119 return false 120 } 121 122 func selectorMatches(selector *metav1.LabelSelector, labels klabels.Set) bool { 123 // From webhook spec: "Default to the empty LabelSelector, which matchesWebhooks everything." 124 if selector == nil { 125 return true 126 } 127 s, err := metav1.LabelSelectorAsSelector(selector) 128 if err != nil { 129 return false 130 } 131 return s.Matches(labels) 132 }