istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/injection/injection.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  	"encoding/json"
    19  	"fmt"
    20  	"strings"
    21  
    22  	v1 "k8s.io/api/core/v1"
    23  
    24  	"istio.io/api/annotation"
    25  	"istio.io/api/label"
    26  	"istio.io/istio/pkg/config"
    27  	"istio.io/istio/pkg/config/analysis"
    28  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    29  	"istio.io/istio/pkg/config/analysis/msg"
    30  	"istio.io/istio/pkg/config/constants"
    31  	"istio.io/istio/pkg/config/resource"
    32  	"istio.io/istio/pkg/config/schema/gvk"
    33  	"istio.io/istio/pkg/slices"
    34  )
    35  
    36  // Analyzer checks conditions related to Istio sidecar injection.
    37  type Analyzer struct{}
    38  
    39  var _ analysis.Analyzer = &Analyzer{}
    40  
    41  // We assume that enablement is via an istio-injection=enabled or istio.io/rev namespace label
    42  // In theory, there can be alternatives using Mutatingwebhookconfiguration, but they're very uncommon
    43  // See https://istio.io/docs/ops/troubleshooting/injection/ for more info.
    44  var (
    45  	RevisionInjectionLabelName = label.IoIstioRev.Name
    46  )
    47  
    48  // Metadata implements Analyzer
    49  func (a *Analyzer) Metadata() analysis.Metadata {
    50  	return analysis.Metadata{
    51  		Name:        "injection.Analyzer",
    52  		Description: "Checks conditions related to Istio sidecar injection",
    53  		Inputs: []config.GroupVersionKind{
    54  			gvk.Namespace,
    55  			gvk.Pod,
    56  			gvk.ConfigMap,
    57  		},
    58  	}
    59  }
    60  
    61  // Analyze implements Analyzer
    62  func (a *Analyzer) Analyze(c analysis.Context) {
    63  	enableNamespacesByDefault := false
    64  	injectedNamespaces := make(map[string]bool)
    65  
    66  	c.ForEach(gvk.Namespace, func(r *resource.Instance) bool {
    67  		if r.Metadata.FullName.String() == constants.IstioSystemNamespace {
    68  			return true
    69  		}
    70  
    71  		ns := r.Metadata.FullName.String()
    72  
    73  		injectionLabel, okInjectionLabel := r.Metadata.Labels[util.InjectionLabelName]
    74  		nsRevision, okNewInjectionLabel := r.Metadata.Labels[RevisionInjectionLabelName]
    75  
    76  		istioLabels := make([]string, 0)
    77  		if okInjectionLabel {
    78  			istioLabels = append(istioLabels, fmt.Sprintf("%s=%s", util.InjectionLabelName, injectionLabel))
    79  		}
    80  		for _, l := range []string{RevisionInjectionLabelName, constants.DataplaneModeLabel} {
    81  			if _, ok := r.Metadata.Labels[l]; ok && (!okInjectionLabel || injectionLabel == "enabled") {
    82  				istioLabels = append(istioLabels, fmt.Sprintf("%s=%s", l, r.Metadata.Labels[l]))
    83  			}
    84  		}
    85  		if len(istioLabels) > 1 {
    86  			m := msg.NewNamespaceMultipleInjectionLabels(r, istioLabels)
    87  			c.Report(gvk.Namespace, m)
    88  		}
    89  
    90  		if r.Metadata.Labels[constants.DataplaneModeLabel] == constants.DataplaneModeAmbient {
    91  			return true
    92  		}
    93  
    94  		// verify the enableNamespacesByDefault flag in injection configmaps
    95  		c.ForEach(gvk.ConfigMap, func(r *resource.Instance) bool {
    96  			injectionCMName := util.GetInjectorConfigMapName(nsRevision)
    97  			if r.Metadata.FullName.Name.String() == injectionCMName {
    98  				cm := r.Message.(*v1.ConfigMap)
    99  				enableNamespacesByDefault = GetEnableNamespacesByDefaultFromInjectedConfigMap(cm)
   100  				return false
   101  			}
   102  			return true
   103  		})
   104  
   105  		if injectionLabel == "" && !okNewInjectionLabel {
   106  			// if Istio is installed with sidecarInjectorWebhook.enableNamespacesByDefault=true
   107  			// (in the istio-sidecar-injector configmap), we need to reverse this logic and treat this as an injected namespace
   108  			if enableNamespacesByDefault {
   109  				m := msg.NewNamespaceInjectionEnabledByDefault(r)
   110  				c.Report(gvk.Namespace, m)
   111  				return true
   112  			}
   113  
   114  			m := msg.NewNamespaceNotInjected(r, ns, ns)
   115  
   116  			if line, ok := util.ErrorLine(r, fmt.Sprintf(util.MetadataName)); ok {
   117  				m.Line = line
   118  			}
   119  
   120  			c.Report(gvk.Namespace, m)
   121  			return true
   122  		}
   123  
   124  		if injectionLabel != util.InjectionLabelEnableValue {
   125  			// If legacy label has any value other than the enablement value, they are deliberately not injecting it, so ignore
   126  			return true
   127  		}
   128  
   129  		injectedNamespaces[ns] = true
   130  
   131  		return true
   132  	})
   133  
   134  	c.ForEach(gvk.Pod, func(r *resource.Instance) bool {
   135  		pod := r.Message.(*v1.PodSpec)
   136  
   137  		if !injectedNamespaces[r.Metadata.FullName.Namespace.String()] {
   138  			return true
   139  		}
   140  
   141  		// If a pod has injection explicitly disabled, no need to check further
   142  		inj := r.Metadata.Annotations[annotation.SidecarInject.Name]
   143  		if v, ok := r.Metadata.Labels[label.SidecarInject.Name]; ok {
   144  			inj = v
   145  		}
   146  		if strings.EqualFold(inj, "false") {
   147  			return true
   148  		}
   149  
   150  		if pod.HostNetwork {
   151  			return true
   152  		}
   153  
   154  		proxyImage := ""
   155  		for _, container := range append(slices.Clone(pod.Containers), pod.InitContainers...) {
   156  			if container.Name == util.IstioProxyName {
   157  				proxyImage = container.Image
   158  				break
   159  			}
   160  		}
   161  
   162  		if proxyImage == "" {
   163  			c.Report(gvk.Pod, msg.NewPodMissingProxy(r, r.Metadata.FullName.String()))
   164  		}
   165  
   166  		return true
   167  	})
   168  }
   169  
   170  // GetInjectedConfigMapValuesStruct retrieves value of sidecarInjectorWebhook.enableNamespacesByDefault
   171  // defined in the sidecar injector configuration.
   172  func GetEnableNamespacesByDefaultFromInjectedConfigMap(cm *v1.ConfigMap) bool {
   173  	var injectedCMValues map[string]any
   174  	if err := json.Unmarshal([]byte(cm.Data[util.InjectionConfigMapValue]), &injectedCMValues); err != nil {
   175  		return false
   176  	}
   177  
   178  	injectionEnable := injectedCMValues[util.InjectorWebhookConfigKey].(map[string]any)[util.InjectorWebhookConfigValue]
   179  	return injectionEnable.(bool)
   180  }