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  }