istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/sidecar/selector.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 sidecar
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"k8s.io/apimachinery/pkg/labels"
    21  
    22  	"istio.io/api/networking/v1alpha3"
    23  	"istio.io/istio/pkg/config"
    24  	"istio.io/istio/pkg/config/analysis"
    25  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    26  	"istio.io/istio/pkg/config/analysis/msg"
    27  	"istio.io/istio/pkg/config/resource"
    28  	"istio.io/istio/pkg/config/schema/gvk"
    29  )
    30  
    31  // SelectorAnalyzer validates, per namespace, that:
    32  // * sidecar resources that define a workload selector match at least one pod
    33  // * there aren't multiple sidecar resources that select overlapping pods
    34  type SelectorAnalyzer struct{}
    35  
    36  var _ analysis.Analyzer = &SelectorAnalyzer{}
    37  
    38  // Metadata implements Analyzer
    39  func (a *SelectorAnalyzer) Metadata() analysis.Metadata {
    40  	return analysis.Metadata{
    41  		Name: "sidecar.SelectorAnalyzer",
    42  		Description: "Validates that sidecars that define a workload selector " +
    43  			"match at least one pod, and that there aren't multiple sidecar resources that select overlapping pods",
    44  		Inputs: []config.GroupVersionKind{
    45  			gvk.Sidecar,
    46  			gvk.Pod,
    47  			gvk.Namespace,
    48  		},
    49  	}
    50  }
    51  
    52  // Analyze implements Analyzer
    53  func (a *SelectorAnalyzer) Analyze(c analysis.Context) {
    54  	podsToSidecars := make(map[resource.FullName][]*resource.Instance)
    55  	pods := make(map[resource.FullName]*resource.Instance)
    56  	namespacesToSidecars := make(map[resource.Namespace][]*resource.Instance)
    57  	namespaces := make(map[string]*resource.Instance)
    58  
    59  	c.ForEach(gvk.Sidecar, func(rs *resource.Instance) bool {
    60  		s := rs.Message.(*v1alpha3.Sidecar)
    61  
    62  		// record namespace-scoped sidecars
    63  		if s.WorkloadSelector == nil || len(s.WorkloadSelector.Labels) == 0 {
    64  			namespacesToSidecars[rs.Metadata.FullName.Namespace] = append(namespacesToSidecars[rs.Metadata.FullName.Namespace], rs)
    65  			return true
    66  		}
    67  
    68  		sNs := rs.Metadata.FullName.Namespace
    69  		sel := labels.SelectorFromSet(s.WorkloadSelector.Labels)
    70  
    71  		foundPod := false
    72  		c.ForEach(gvk.Pod, func(rp *resource.Instance) bool {
    73  			pNs := rp.Metadata.FullName.Namespace
    74  			podLabels := labels.Set(rp.Metadata.Labels)
    75  
    76  			// Only attempt to match in the same namespace
    77  			if pNs != sNs {
    78  				return true
    79  			}
    80  
    81  			if sel.Matches(podLabels) {
    82  				foundPod = true
    83  				podsToSidecars[rp.Metadata.FullName] = append(podsToSidecars[rp.Metadata.FullName], rs)
    84  				pods[rp.Metadata.FullName] = rp
    85  			}
    86  
    87  			return true
    88  		})
    89  
    90  		if !foundPod {
    91  			m := msg.NewReferencedResourceNotFound(rs, "selector", sel.String())
    92  
    93  			label := util.ExtractLabelFromSelectorString(sel.String())
    94  			if line, ok := util.ErrorLine(rs, fmt.Sprintf(util.WorkloadSelector, label)); ok {
    95  				m.Line = line
    96  			}
    97  
    98  			c.Report(gvk.Sidecar, m)
    99  		}
   100  
   101  		return true
   102  	})
   103  
   104  	c.ForEach(gvk.Namespace, func(r *resource.Instance) bool {
   105  		namespaces[r.Metadata.FullName.Name.String()] = r
   106  		return true
   107  	})
   108  
   109  	reportedResources := make(map[string]bool)
   110  	for p, sList := range podsToSidecars {
   111  		podResource := pods[p]
   112  
   113  		if len(sList) == 1 && !util.PodInAmbientMode(podResource) {
   114  			continue
   115  		}
   116  
   117  		sNames := getNames(sList)
   118  
   119  		for _, rs := range sList {
   120  			// We don't want to report errors for pods in ambient mode, since there is no sidecar,
   121  			// but we do want to warn that the policy is ineffective.
   122  			if util.PodInAmbientMode(podResource) {
   123  				if !reportedResources[rs.Metadata.FullName.String()] {
   124  					c.Report(gvk.Sidecar, msg.NewIneffectivePolicy(rs,
   125  						"selected workload is in ambient mode, the policy has no impact"))
   126  					reportedResources[rs.Metadata.FullName.String()] = true
   127  				}
   128  				continue
   129  			}
   130  
   131  			m := msg.NewConflictingSidecarWorkloadSelectors(rs, sNames, p.Namespace.String(), p.Name.String())
   132  
   133  			if line, ok := util.ErrorLine(rs, fmt.Sprintf(util.MetadataName)); ok {
   134  				m.Line = line
   135  			}
   136  
   137  			c.Report(gvk.Sidecar, m)
   138  		}
   139  	}
   140  
   141  	for ns, sList := range namespacesToSidecars {
   142  		nsResource := namespaces[ns.String()]
   143  		// We don't want to report errors for namespaces in ambient mode, since there is no sidecar,
   144  		// but we do want to warn that the policy is ineffective.
   145  		// TODO: do we need to check mixed mode?
   146  		if util.NamespaceInAmbientMode(nsResource) {
   147  			for _, rs := range sList {
   148  				if !reportedResources[rs.Metadata.FullName.String()] {
   149  					c.Report(gvk.Sidecar, msg.NewIneffectivePolicy(rs,
   150  						"namespace is in ambient mode, the policy has no impact"))
   151  					reportedResources[rs.Metadata.FullName.String()] = true
   152  				}
   153  			}
   154  			continue
   155  		}
   156  		if len(sList) > 1 {
   157  			sNames := getNames(sList)
   158  			for _, r := range sList {
   159  				c.Report(gvk.Sidecar, msg.NewMultipleSidecarsWithoutWorkloadSelectors(r, sNames, string(ns)))
   160  			}
   161  		}
   162  	}
   163  }