istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/inject/inject.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 inject
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"reflect"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"text/template"
    28  
    29  	"github.com/Masterminds/sprig/v3"
    30  	jsonpatch "github.com/evanphx/json-patch/v5"
    31  	appsv1 "k8s.io/api/apps/v1"
    32  	batch "k8s.io/api/batch/v1"
    33  	corev1 "k8s.io/api/core/v1"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/labels"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/runtime/schema"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
    40  	"sigs.k8s.io/yaml"
    41  
    42  	"istio.io/api/annotation"
    43  	"istio.io/api/label"
    44  	meshconfig "istio.io/api/mesh/v1alpha1"
    45  	proxyConfig "istio.io/api/networking/v1beta1"
    46  	opconfig "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
    47  	"istio.io/istio/pilot/pkg/features"
    48  	"istio.io/istio/pkg/config"
    49  	"istio.io/istio/pkg/config/mesh"
    50  	common_features "istio.io/istio/pkg/features"
    51  	"istio.io/istio/pkg/kube"
    52  	"istio.io/istio/pkg/log"
    53  	"istio.io/istio/tools/istio-iptables/pkg/constants"
    54  )
    55  
    56  // InjectionPolicy determines the policy for injecting the
    57  // sidecar proxy into the watched namespace(s).
    58  type InjectionPolicy string
    59  
    60  const (
    61  	// InjectionPolicyDisabled specifies that the sidecar injector
    62  	// will not inject the sidecar into resources by default for the
    63  	// namespace(s) being watched. Resources can enable injection
    64  	// using the "sidecar.istio.io/inject" annotation with value of
    65  	// true.
    66  	InjectionPolicyDisabled InjectionPolicy = "disabled"
    67  
    68  	// InjectionPolicyEnabled specifies that the sidecar injector will
    69  	// inject the sidecar into resources by default for the
    70  	// namespace(s) being watched. Resources can disable injection
    71  	// using the "sidecar.istio.io/inject" annotation with value of
    72  	// false.
    73  	InjectionPolicyEnabled InjectionPolicy = "enabled"
    74  )
    75  
    76  const (
    77  	// ProxyContainerName is used by e2e integration tests for fetching logs
    78  	ProxyContainerName = "istio-proxy"
    79  
    80  	// ValidationContainerName is the name of the init container that validates
    81  	// if CNI has made the necessary changes to iptables
    82  	ValidationContainerName = "istio-validation"
    83  
    84  	// InitContainerName is the name of the init container that deploys iptables
    85  	InitContainerName = "istio-init"
    86  
    87  	// EnableCoreDumpName is the name of the init container that allows core dumps
    88  	EnableCoreDumpName = "enable-core-dump"
    89  )
    90  
    91  const (
    92  	// ImageTypeDebug is the suffix of the debug image.
    93  	ImageTypeDebug = "debug"
    94  	// ImageTypeDistroless is the suffix of the distroless image.
    95  	ImageTypeDistroless = "distroless"
    96  	// ImageTypeDefault is the type name of the default image, sufix is elided.
    97  	ImageTypeDefault = "default"
    98  )
    99  
   100  // SidecarTemplateData is the data object to which the templated
   101  // version of `SidecarInjectionSpec` is applied.
   102  type SidecarTemplateData struct {
   103  	TypeMeta                 metav1.TypeMeta
   104  	DeploymentMeta           types.NamespacedName
   105  	ObjectMeta               metav1.ObjectMeta
   106  	Spec                     corev1.PodSpec
   107  	ProxyConfig              *meshconfig.ProxyConfig
   108  	MeshConfig               *meshconfig.MeshConfig
   109  	Values                   map[string]any
   110  	Revision                 string
   111  	ProxyImage               string
   112  	ProxyUID                 int64
   113  	ProxyGID                 int64
   114  	InboundTrafficPolicyMode string
   115  	CompliancePolicy         string
   116  }
   117  
   118  type (
   119  	Template     *corev1.Pod
   120  	RawTemplates map[string]string
   121  	Templates    map[string]*template.Template
   122  )
   123  
   124  type Injector interface {
   125  	Inject(pod *corev1.Pod, namespace string) ([]byte, error)
   126  }
   127  
   128  // Config specifies the sidecar injection configuration This includes
   129  // the sidecar template and cluster-side injection policy. It is used
   130  // by kube-inject, sidecar injector, and http endpoint.
   131  type Config struct {
   132  	Policy InjectionPolicy `json:"policy"`
   133  
   134  	// DefaultTemplates defines the default template to use for pods that do not explicitly specify a template
   135  	DefaultTemplates []string `json:"defaultTemplates"`
   136  
   137  	// RawTemplates defines a set of templates to be used. The specified template will be run, provided with
   138  	// SidecarTemplateData, and merged with the original pod spec using a strategic merge patch.
   139  	RawTemplates RawTemplates `json:"templates"`
   140  
   141  	// Aliases defines a translation of a name to inject template. For example, `sidecar: [proxy,init]` could allow
   142  	// referencing two templates, "proxy" and "init" by a single name, "sidecar".
   143  	// Expansion is not recursive.
   144  	Aliases map[string][]string `json:"aliases"`
   145  
   146  	// NeverInjectSelector: Refuses the injection on pods whose labels match this selector.
   147  	// It's an array of label selectors, that will be OR'ed, meaning we will iterate
   148  	// over it and stop at the first match
   149  	// Takes precedence over AlwaysInjectSelector.
   150  	NeverInjectSelector []metav1.LabelSelector `json:"neverInjectSelector"`
   151  
   152  	// AlwaysInjectSelector: Forces the injection on pods whose labels match this selector.
   153  	// It's an array of label selectors, that will be OR'ed, meaning we will iterate
   154  	// over it and stop at the first match
   155  	AlwaysInjectSelector []metav1.LabelSelector `json:"alwaysInjectSelector"`
   156  
   157  	// InjectedAnnotations are additional annotations that will be added to the pod spec after injection
   158  	// This is primarily to support PSP annotations.
   159  	InjectedAnnotations map[string]string `json:"injectedAnnotations"`
   160  
   161  	// Templates is a pre-parsed copy of RawTemplates
   162  	Templates Templates `json:"-"`
   163  }
   164  
   165  const (
   166  	SidecarTemplateName = "sidecar"
   167  )
   168  
   169  // UnmarshalConfig unmarshals the provided YAML configuration, while normalizing the resulting configuration
   170  func UnmarshalConfig(yml []byte) (Config, error) {
   171  	var injectConfig Config
   172  	if err := yaml.Unmarshal(yml, &injectConfig); err != nil {
   173  		return injectConfig, fmt.Errorf("failed to unmarshal injection template: %v", err)
   174  	}
   175  	if injectConfig.RawTemplates == nil {
   176  		injectConfig.RawTemplates = make(map[string]string)
   177  	}
   178  	if len(injectConfig.DefaultTemplates) == 0 {
   179  		injectConfig.DefaultTemplates = []string{SidecarTemplateName}
   180  	}
   181  	if len(injectConfig.RawTemplates) == 0 {
   182  		log.Warnf("injection templates are empty." +
   183  			" This may be caused by using an injection template from an older version of Istio." +
   184  			" Please ensure the template is correct; mismatch template versions can lead to unexpected results, including pods not being injected.")
   185  	}
   186  
   187  	var err error
   188  	injectConfig.Templates, err = ParseTemplates(injectConfig.RawTemplates)
   189  	if err != nil {
   190  		return injectConfig, err
   191  	}
   192  
   193  	return injectConfig, nil
   194  }
   195  
   196  func injectRequired(ignored []string, config *Config, podSpec *corev1.PodSpec, metadata metav1.ObjectMeta) bool { // nolint: lll
   197  	// Skip injection when host networking is enabled. The problem is
   198  	// that the iptables changes are assumed to be within the pod when,
   199  	// in fact, they are changing the routing at the host level. This
   200  	// often results in routing failures within a node which can
   201  	// affect the network provider within the cluster causing
   202  	// additional pod failures.
   203  	if podSpec.HostNetwork {
   204  		return false
   205  	}
   206  
   207  	// skip special kubernetes system namespaces
   208  	for _, namespace := range ignored {
   209  		if metadata.Namespace == namespace {
   210  			return false
   211  		}
   212  	}
   213  
   214  	annos := metadata.GetAnnotations()
   215  
   216  	var useDefault bool
   217  	var inject bool
   218  
   219  	objectSelector := annos[annotation.SidecarInject.Name]
   220  	if lbl, labelPresent := metadata.GetLabels()[label.SidecarInject.Name]; labelPresent {
   221  		// The label is the new API; if both are present we prefer the label
   222  		objectSelector = lbl
   223  	}
   224  	switch strings.ToLower(objectSelector) {
   225  	// http://yaml.org/type/bool.html
   226  	case "y", "yes", "true", "on":
   227  		inject = true
   228  	case "":
   229  		useDefault = true
   230  	}
   231  
   232  	// If an annotation is not explicitly given, check the LabelSelectors, starting with NeverInject
   233  	if useDefault {
   234  		for _, neverSelector := range config.NeverInjectSelector {
   235  			selector, err := metav1.LabelSelectorAsSelector(&neverSelector)
   236  			if err != nil {
   237  				log.Warnf("Invalid selector for NeverInjectSelector: %v (%v)", neverSelector, err)
   238  			} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)) {
   239  				log.Debugf("Explicitly disabling injection for pod %s/%s due to pod labels matching NeverInjectSelector config map entry.",
   240  					metadata.Namespace, potentialPodName(metadata))
   241  				inject = false
   242  				useDefault = false
   243  				break
   244  			}
   245  		}
   246  	}
   247  
   248  	// If there's no annotation nor a NeverInjectSelector, check the AlwaysInject one
   249  	if useDefault {
   250  		for _, alwaysSelector := range config.AlwaysInjectSelector {
   251  			selector, err := metav1.LabelSelectorAsSelector(&alwaysSelector)
   252  			if err != nil {
   253  				log.Warnf("Invalid selector for AlwaysInjectSelector: %v (%v)", alwaysSelector, err)
   254  			} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)) {
   255  				log.Debugf("Explicitly enabling injection for pod %s/%s due to pod labels matching AlwaysInjectSelector config map entry.",
   256  					metadata.Namespace, potentialPodName(metadata))
   257  				inject = true
   258  				useDefault = false
   259  				break
   260  			}
   261  		}
   262  	}
   263  
   264  	var required bool
   265  	switch config.Policy {
   266  	default: // InjectionPolicyOff
   267  		log.Errorf("Illegal value for autoInject:%s, must be one of [%s,%s]. Auto injection disabled!",
   268  			config.Policy, InjectionPolicyDisabled, InjectionPolicyEnabled)
   269  		required = false
   270  	case InjectionPolicyDisabled:
   271  		if useDefault {
   272  			required = false
   273  		} else {
   274  			required = inject
   275  		}
   276  	case InjectionPolicyEnabled:
   277  		if useDefault {
   278  			required = true
   279  		} else {
   280  			required = inject
   281  		}
   282  	}
   283  
   284  	if log.DebugEnabled() {
   285  		// Build a log message for the annotations.
   286  		annotationStr := ""
   287  		for name := range AnnotationValidation {
   288  			value, ok := annos[name]
   289  			if !ok {
   290  				value = "(unset)"
   291  			}
   292  			annotationStr += fmt.Sprintf("%s:%s ", name, value)
   293  		}
   294  
   295  		log.Debugf("Sidecar injection policy for %v/%v: namespacePolicy:%v useDefault:%v inject:%v required:%v %s",
   296  			metadata.Namespace,
   297  			potentialPodName(metadata),
   298  			config.Policy,
   299  			useDefault,
   300  			inject,
   301  			required,
   302  			annotationStr)
   303  	}
   304  
   305  	return required
   306  }
   307  
   308  // ProxyImage constructs image url in a backwards compatible way.
   309  // values based name => {{ .Values.global.hub }}/{{ .Values.global.proxy.image }}:{{ .Values.global.tag }}
   310  func ProxyImage(values *opconfig.Values, image *proxyConfig.ProxyImage, annotations map[string]string) string {
   311  	imageName := "proxyv2"
   312  	global := values.GetGlobal()
   313  
   314  	tag := ""
   315  	if global.GetTag() != nil { // Tag is an interface but we need the string form.
   316  		tag = fmt.Sprintf("%v", global.GetTag().AsInterface())
   317  	}
   318  
   319  	imageType := global.GetVariant()
   320  	if image != nil {
   321  		imageType = image.ImageType
   322  	}
   323  
   324  	if global.GetProxy() != nil && global.GetProxy().GetImage() != "" {
   325  		imageName = global.GetProxy().GetImage()
   326  	}
   327  
   328  	if it, ok := annotations[annotation.SidecarProxyImageType.Name]; ok {
   329  		imageType = it
   330  	}
   331  
   332  	return imageURL(global.GetHub(), imageName, tag, imageType)
   333  }
   334  
   335  func InboundTrafficPolicyMode(meshConfig *meshconfig.MeshConfig) string {
   336  	switch meshConfig.GetInboundTrafficPolicy().GetMode() {
   337  	case meshconfig.MeshConfig_InboundTrafficPolicy_LOCALHOST:
   338  		return "localhost"
   339  	case meshconfig.MeshConfig_InboundTrafficPolicy_PASSTHROUGH:
   340  		return "passthrough"
   341  	}
   342  	return "passthrough"
   343  }
   344  
   345  // imageURL creates url from parts.
   346  // imageType is appended if not empty
   347  // if imageType is already present in the tag, then it is replaced.
   348  // docker.io/istio/proxyv2:1.12-distroless
   349  // gcr.io/gke-release/asm/proxyv2:1.11.2-asm.17-distroless
   350  // docker.io/istio/proxyv2:1.12
   351  func imageURL(hub, imageName, tag, imageType string) string {
   352  	return hub + "/" + imageName + ":" + updateImageTypeIfPresent(tag, imageType)
   353  }
   354  
   355  // KnownImageTypes are image types that istio pubishes.
   356  var KnownImageTypes = []string{ImageTypeDistroless, ImageTypeDebug}
   357  
   358  func updateImageTypeIfPresent(tag string, imageType string) string {
   359  	if imageType == "" {
   360  		return tag
   361  	}
   362  
   363  	for _, i := range KnownImageTypes {
   364  		if strings.HasSuffix(tag, "-"+i) {
   365  			tag = tag[:len(tag)-(len(i)+1)]
   366  			break
   367  		}
   368  	}
   369  
   370  	if imageType == ImageTypeDefault {
   371  		return tag
   372  	}
   373  
   374  	return tag + "-" + imageType
   375  }
   376  
   377  func extractClusterAndNetwork(params InjectionParameters) (string, string) {
   378  	metadata := &params.pod.ObjectMeta
   379  	cluster := params.valuesConfig.asStruct.GetGlobal().GetMultiCluster().GetClusterName()
   380  	// TODO allow overriding the values.global network in injection with the system namespace label
   381  	network := params.valuesConfig.asStruct.GetGlobal().GetNetwork()
   382  	// params may be set from webhook URL, take priority over values yaml
   383  	if params.proxyEnvs["ISTIO_META_CLUSTER_ID"] != "" {
   384  		cluster = params.proxyEnvs["ISTIO_META_CLUSTER_ID"]
   385  	}
   386  	if params.proxyEnvs["ISTIO_META_NETWORK"] != "" {
   387  		network = params.proxyEnvs["ISTIO_META_NETWORK"]
   388  	}
   389  	// explicit label takes highest precedence
   390  	if n, ok := metadata.Labels[label.TopologyNetwork.Name]; ok {
   391  		network = n
   392  	}
   393  	return cluster, network
   394  }
   395  
   396  // RunTemplate renders the sidecar template
   397  // Returns the raw string template, as well as the parse pod form
   398  func RunTemplate(params InjectionParameters) (mergedPod *corev1.Pod, templatePod *corev1.Pod, err error) {
   399  	metadata := &params.pod.ObjectMeta
   400  	meshConfig := params.meshConfig
   401  
   402  	if err := validateAnnotations(metadata.GetAnnotations()); err != nil {
   403  		log.Errorf("Injection failed due to invalid annotations: %v", err)
   404  		return nil, nil, err
   405  	}
   406  
   407  	cluster, network := extractClusterAndNetwork(params)
   408  
   409  	// use network in values for template, and proxy env variables
   410  	if cluster != "" {
   411  		params.proxyEnvs["ISTIO_META_CLUSTER_ID"] = cluster
   412  	}
   413  	if network != "" {
   414  		params.proxyEnvs["ISTIO_META_NETWORK"] = network
   415  	}
   416  
   417  	strippedPod, err := reinsertOverrides(stripPod(params))
   418  	if err != nil {
   419  		return nil, nil, err
   420  	}
   421  
   422  	proxyUID, proxyGID := GetProxyIDs(params.namespace)
   423  
   424  	// When changing this, make sure to change TemplateInput in deploymentcontroller.go
   425  	data := SidecarTemplateData{
   426  		TypeMeta:                 params.typeMeta,
   427  		DeploymentMeta:           params.deployMeta,
   428  		ObjectMeta:               strippedPod.ObjectMeta,
   429  		Spec:                     strippedPod.Spec,
   430  		ProxyConfig:              params.proxyConfig,
   431  		MeshConfig:               meshConfig,
   432  		Values:                   params.valuesConfig.asMap,
   433  		Revision:                 params.revision,
   434  		ProxyImage:               ProxyImage(params.valuesConfig.asStruct, params.proxyConfig.Image, strippedPod.Annotations),
   435  		ProxyUID:                 proxyUID,
   436  		ProxyGID:                 proxyGID,
   437  		InboundTrafficPolicyMode: InboundTrafficPolicyMode(meshConfig),
   438  		CompliancePolicy:         common_features.CompliancePolicy,
   439  	}
   440  
   441  	mergedPod = params.pod
   442  	templatePod = &corev1.Pod{}
   443  	for _, templateName := range selectTemplates(params) {
   444  		parsedTemplate, f := params.templates[templateName]
   445  		if !f {
   446  			return nil, nil, fmt.Errorf("requested template %q not found; have %v",
   447  				templateName, strings.Join(knownTemplates(params.templates), ", "))
   448  		}
   449  		bbuf, err := runTemplate(parsedTemplate, data)
   450  		if err != nil {
   451  			return nil, nil, err
   452  		}
   453  
   454  		templatePod, err = applyOverlayYAML(templatePod, bbuf.Bytes())
   455  		if err != nil {
   456  			return nil, nil, fmt.Errorf("failed applying injection overlay: %v", err)
   457  		}
   458  		// This is a bit of a weird hack. With NativeSidecars, the container will be under initContainers in the template pod.
   459  		// But we may have injection customizations (https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/#customizing-injection);
   460  		// these will be in the `containers` field.
   461  		// So if we see the proxy container in `containers` in the original pod, and in `initContainers` in the template pod,
   462  		// move the container.
   463  		if features.EnableNativeSidecars.Get() &&
   464  			FindContainer(ProxyContainerName, templatePod.Spec.InitContainers) != nil &&
   465  			FindContainer(ProxyContainerName, mergedPod.Spec.Containers) != nil {
   466  			mergedPod = mergedPod.DeepCopy()
   467  			mergedPod.Spec.Containers, mergedPod.Spec.InitContainers = moveContainer(mergedPod.Spec.Containers, mergedPod.Spec.InitContainers, ProxyContainerName)
   468  		}
   469  		mergedPod, err = applyOverlayYAML(mergedPod, bbuf.Bytes())
   470  		if err != nil {
   471  			return nil, nil, fmt.Errorf("failed parsing generated injected YAML (check Istio sidecar injector configuration): %v", err)
   472  		}
   473  	}
   474  
   475  	return mergedPod, templatePod, nil
   476  }
   477  
   478  func knownTemplates(t Templates) []string {
   479  	keys := make([]string, 0, len(t))
   480  	for k := range t {
   481  		keys = append(keys, k)
   482  	}
   483  	return keys
   484  }
   485  
   486  func selectTemplates(params InjectionParameters) []string {
   487  	if a, f := params.pod.Annotations[annotation.InjectTemplates.Name]; f {
   488  		names := []string{}
   489  		for _, tmplName := range strings.Split(a, ",") {
   490  			name := strings.TrimSpace(tmplName)
   491  			names = append(names, name)
   492  		}
   493  		return resolveAliases(params, names)
   494  	}
   495  	return resolveAliases(params, params.defaultTemplate)
   496  }
   497  
   498  func resolveAliases(params InjectionParameters, names []string) []string {
   499  	ret := []string{}
   500  	for _, name := range names {
   501  		if al, f := params.aliases[name]; f {
   502  			ret = append(ret, al...)
   503  		} else {
   504  			ret = append(ret, name)
   505  		}
   506  	}
   507  	return ret
   508  }
   509  
   510  func stripPod(req InjectionParameters) *corev1.Pod {
   511  	pod := req.pod.DeepCopy()
   512  	prevStatus := injectionStatus(pod)
   513  	if prevStatus == nil {
   514  		return req.pod
   515  	}
   516  	// We found a previous status annotation. Possibly we are re-injecting the pod
   517  	// To ensure idempotency, remove our injected containers first
   518  	for _, c := range prevStatus.Containers {
   519  		pod.Spec.Containers = modifyContainers(pod.Spec.Containers, c, Remove)
   520  	}
   521  	for _, c := range prevStatus.InitContainers {
   522  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, c, Remove)
   523  	}
   524  
   525  	targetPort := strconv.Itoa(int(req.meshConfig.GetDefaultConfig().GetStatusPort()))
   526  	if cur, f := getPrometheusPort(pod); f {
   527  		// We have already set the port, assume user is controlling this or, more likely, re-injected
   528  		// the pod.
   529  		if cur == targetPort {
   530  			clearPrometheusAnnotations(pod)
   531  		}
   532  	}
   533  	delete(pod.Annotations, annotation.SidecarStatus.Name)
   534  
   535  	return pod
   536  }
   537  
   538  func injectionStatus(pod *corev1.Pod) *SidecarInjectionStatus {
   539  	var statusBytes []byte
   540  	if pod.ObjectMeta.Annotations != nil {
   541  		if value, ok := pod.ObjectMeta.Annotations[annotation.SidecarStatus.Name]; ok {
   542  			statusBytes = []byte(value)
   543  		}
   544  	}
   545  	if statusBytes == nil {
   546  		return nil
   547  	}
   548  
   549  	// default case when injected pod has explicit status
   550  	var iStatus SidecarInjectionStatus
   551  	if err := json.Unmarshal(statusBytes, &iStatus); err != nil {
   552  		return nil
   553  	}
   554  	return &iStatus
   555  }
   556  
   557  func parseDryTemplate(tmplStr string, funcMap map[string]any) (*template.Template, error) {
   558  	temp := template.New("inject")
   559  	t, err := temp.Funcs(sprig.TxtFuncMap()).Funcs(funcMap).Parse(tmplStr)
   560  	if err != nil {
   561  		log.Infof("Failed to parse template: %v %v\n", err, tmplStr)
   562  		return nil, err
   563  	}
   564  
   565  	return t, nil
   566  }
   567  
   568  func runTemplate(tmpl *template.Template, data SidecarTemplateData) (bytes.Buffer, error) {
   569  	var res bytes.Buffer
   570  	if err := tmpl.Execute(&res, &data); err != nil {
   571  		log.Errorf("Invalid template: %v", err)
   572  		return bytes.Buffer{}, err
   573  	}
   574  
   575  	return res, nil
   576  }
   577  
   578  // IntoResourceFile injects the istio proxy into the specified
   579  // kubernetes YAML file.
   580  // nolint: lll
   581  func IntoResourceFile(injector Injector, sidecarTemplate Templates,
   582  	valuesConfig ValuesConfig, revision string, meshconfig *meshconfig.MeshConfig, in io.Reader, out io.Writer, warningHandler func(string),
   583  ) error {
   584  	reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(in, 4096))
   585  	for {
   586  		raw, err := reader.Read()
   587  		if err == io.EOF {
   588  			break
   589  		}
   590  		if err != nil {
   591  			return err
   592  		}
   593  
   594  		obj, err := FromRawToObject(raw)
   595  		if err != nil && !runtime.IsNotRegisteredError(err) {
   596  			return err
   597  		}
   598  
   599  		var updated []byte
   600  		if err == nil {
   601  			outObject, err := IntoObject(injector, sidecarTemplate, valuesConfig, revision, meshconfig, obj, warningHandler) // nolint: vetshadow
   602  			if err != nil {
   603  				return err
   604  			}
   605  			if updated, err = yaml.Marshal(outObject); err != nil {
   606  				return err
   607  			}
   608  		} else {
   609  			updated = raw // unchanged
   610  		}
   611  
   612  		if _, err = out.Write(updated); err != nil {
   613  			return err
   614  		}
   615  		if _, err = fmt.Fprint(out, "---\n"); err != nil {
   616  			return err
   617  		}
   618  	}
   619  	return nil
   620  }
   621  
   622  // FromRawToObject is used to convert from raw to the runtime object
   623  func FromRawToObject(raw []byte) (runtime.Object, error) {
   624  	var typeMeta metav1.TypeMeta
   625  	if err := yaml.Unmarshal(raw, &typeMeta); err != nil {
   626  		return nil, err
   627  	}
   628  
   629  	gvk := schema.FromAPIVersionAndKind(typeMeta.APIVersion, typeMeta.Kind)
   630  	obj, err := injectScheme.New(gvk)
   631  	if err != nil {
   632  		return nil, err
   633  	}
   634  	if err = yaml.Unmarshal(raw, obj); err != nil {
   635  		return nil, err
   636  	}
   637  
   638  	return obj, nil
   639  }
   640  
   641  // IntoObject convert the incoming resources into Injected resources
   642  // nolint: lll
   643  func IntoObject(injector Injector, sidecarTemplate Templates, valuesConfig ValuesConfig,
   644  	revision string, meshconfig *meshconfig.MeshConfig, in runtime.Object, warningHandler func(string),
   645  ) (any, error) {
   646  	out := in.DeepCopyObject()
   647  
   648  	var deploymentMetadata types.NamespacedName
   649  	var metadata *metav1.ObjectMeta
   650  	var podSpec *corev1.PodSpec
   651  	var typeMeta metav1.TypeMeta
   652  
   653  	// Handle Lists
   654  	if list, ok := out.(*corev1.List); ok {
   655  		result := list
   656  
   657  		for i, item := range list.Items {
   658  			obj, err := FromRawToObject(item.Raw)
   659  			if runtime.IsNotRegisteredError(err) {
   660  				continue
   661  			}
   662  			if err != nil {
   663  				return nil, err
   664  			}
   665  
   666  			r, err := IntoObject(injector, sidecarTemplate, valuesConfig, revision, meshconfig, obj, warningHandler) // nolint: vetshadow
   667  			if err != nil {
   668  				return nil, err
   669  			}
   670  
   671  			re := runtime.RawExtension{}
   672  			re.Object = r.(runtime.Object)
   673  			result.Items[i] = re
   674  		}
   675  		return result, nil
   676  	}
   677  
   678  	// CronJobs have JobTemplates in them, instead of Templates, so we
   679  	// special case them.
   680  	switch v := out.(type) {
   681  	case *batch.CronJob:
   682  		job := v
   683  		typeMeta = job.TypeMeta
   684  		metadata = &job.Spec.JobTemplate.ObjectMeta
   685  		deploymentMetadata = config.NamespacedName(job)
   686  		podSpec = &job.Spec.JobTemplate.Spec.Template.Spec
   687  	case *corev1.Pod:
   688  		pod := v
   689  		metadata = &pod.ObjectMeta
   690  		// sync from webhook inject
   691  		deploymentMetadata, typeMeta = kube.GetDeployMetaFromPod(pod)
   692  		podSpec = &pod.Spec
   693  	case *appsv1.Deployment: // Added to be explicit about the most expected case
   694  		deploy := v
   695  		typeMeta = deploy.TypeMeta
   696  		deploymentMetadata = config.NamespacedName(deploy)
   697  		metadata = &deploy.Spec.Template.ObjectMeta
   698  		podSpec = &deploy.Spec.Template.Spec
   699  	default:
   700  		// `in` is a pointer to an Object. Dereference it.
   701  		outValue := reflect.ValueOf(out).Elem()
   702  
   703  		typeMeta = outValue.FieldByName("TypeMeta").Interface().(metav1.TypeMeta)
   704  
   705  		om := outValue.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
   706  		deploymentMetadata = types.NamespacedName{Name: om.GetName(), Namespace: om.GetNamespace()}
   707  
   708  		templateValue := outValue.FieldByName("Spec").FieldByName("Template")
   709  		// `Template` is defined as a pointer in some older API
   710  		// definitions, e.g. ReplicationController
   711  		if templateValue.Kind() == reflect.Ptr {
   712  			if templateValue.IsNil() {
   713  				return out, fmt.Errorf("spec.template is required value")
   714  			}
   715  			templateValue = templateValue.Elem()
   716  		}
   717  		metadata = templateValue.FieldByName("ObjectMeta").Addr().Interface().(*metav1.ObjectMeta)
   718  		podSpec = templateValue.FieldByName("Spec").Addr().Interface().(*corev1.PodSpec)
   719  	}
   720  
   721  	name := metadata.Name
   722  	if name == "" {
   723  		name = deploymentMetadata.Name
   724  	}
   725  	namespace := metadata.Namespace
   726  	if namespace == "" {
   727  		namespace = deploymentMetadata.Namespace
   728  	}
   729  
   730  	var fullName string
   731  	if deploymentMetadata.Namespace != "" {
   732  		fullName = fmt.Sprintf("%s/%s", deploymentMetadata.Namespace, name)
   733  	} else {
   734  		fullName = name
   735  	}
   736  
   737  	kind := typeMeta.Kind
   738  
   739  	// Skip injection when host networking is enabled. The problem is
   740  	// that the iptable changes are assumed to be within the pod when,
   741  	// in fact, they are changing the routing at the host level. This
   742  	// often results in routing failures within a node which can
   743  	// affect the network provider within the cluster causing
   744  	// additional pod failures.
   745  	if podSpec.HostNetwork {
   746  		warningStr := fmt.Sprintf("===> Skipping injection because %q has host networking enabled\n",
   747  			fullName)
   748  		if kind != "" {
   749  			warningStr = fmt.Sprintf("===> Skipping injection because %s %q has host networking enabled\n",
   750  				kind, fullName)
   751  		}
   752  		warningHandler(warningStr)
   753  		return out, nil
   754  	}
   755  
   756  	pod := &corev1.Pod{
   757  		ObjectMeta: *metadata,
   758  		Spec:       *podSpec,
   759  	}
   760  
   761  	var patchBytes []byte
   762  	var err error
   763  	if injector != nil {
   764  		patchBytes, err = injector.Inject(pod, namespace)
   765  	}
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  	// TODO(Monkeyanator) istioctl injection still applies just the pod annotation since we don't have
   770  	// the ProxyConfig CRs here.
   771  	if pca, f := metadata.GetAnnotations()[annotation.ProxyConfig.Name]; f {
   772  		var merr error
   773  		meshconfig, merr = mesh.ApplyProxyConfig(pca, meshconfig)
   774  		if merr != nil {
   775  			return nil, merr
   776  		}
   777  	}
   778  
   779  	if patchBytes == nil {
   780  		if !injectRequired(IgnoredNamespaces.UnsortedList(), &Config{Policy: InjectionPolicyEnabled}, &pod.Spec, pod.ObjectMeta) {
   781  			warningStr := fmt.Sprintf("===> Skipping injection because %q has sidecar injection disabled\n", fullName)
   782  			if kind != "" {
   783  				warningStr = fmt.Sprintf("===> Skipping injection because %s %q has sidecar injection disabled\n",
   784  					kind, fullName)
   785  			}
   786  			warningHandler(warningStr)
   787  			return out, nil
   788  		}
   789  		params := InjectionParameters{
   790  			pod:        pod,
   791  			deployMeta: deploymentMetadata,
   792  			typeMeta:   typeMeta,
   793  			// Todo replace with some template resolver abstraction
   794  			templates:           sidecarTemplate,
   795  			defaultTemplate:     []string{SidecarTemplateName},
   796  			meshConfig:          meshconfig,
   797  			proxyConfig:         meshconfig.GetDefaultConfig(),
   798  			valuesConfig:        valuesConfig,
   799  			revision:            revision,
   800  			proxyEnvs:           map[string]string{},
   801  			injectedAnnotations: nil,
   802  		}
   803  		patchBytes, err = injectPod(params)
   804  	}
   805  	if err != nil {
   806  		return nil, err
   807  	}
   808  	patched, err := applyJSONPatchToPod(pod, patchBytes)
   809  	if err != nil {
   810  		return nil, err
   811  	}
   812  	patchedObject, _, err := jsonSerializer.Decode(patched, nil, &corev1.Pod{})
   813  	if err != nil {
   814  		return nil, err
   815  	}
   816  	patchedPod := patchedObject.(*corev1.Pod)
   817  	*metadata = patchedPod.ObjectMeta
   818  	*podSpec = patchedPod.Spec
   819  	return out, nil
   820  }
   821  
   822  func applyJSONPatchToPod(input *corev1.Pod, patch []byte) ([]byte, error) {
   823  	objJS, err := runtime.Encode(jsonSerializer, input)
   824  	if err != nil {
   825  		return nil, err
   826  	}
   827  
   828  	p, err := jsonpatch.DecodePatch(patch)
   829  	if err != nil {
   830  		return nil, err
   831  	}
   832  
   833  	patchedJSON, err := p.Apply(objJS)
   834  	if err != nil {
   835  		return nil, err
   836  	}
   837  	return patchedJSON, nil
   838  }
   839  
   840  // SidecarInjectionStatus contains basic information about the
   841  // injected sidecar. This includes the names of added containers and
   842  // volumes.
   843  type SidecarInjectionStatus struct {
   844  	InitContainers   []string `json:"initContainers"`
   845  	Containers       []string `json:"containers"`
   846  	Volumes          []string `json:"volumes"`
   847  	ImagePullSecrets []string `json:"imagePullSecrets"`
   848  	Revision         string   `json:"revision"`
   849  }
   850  
   851  func potentialPodName(metadata metav1.ObjectMeta) string {
   852  	if metadata.Name != "" {
   853  		return metadata.Name
   854  	}
   855  	if metadata.GenerateName != "" {
   856  		return metadata.GenerateName + "***** (actual name not yet known)"
   857  	}
   858  	return ""
   859  }
   860  
   861  // overwriteClusterInfo updates cluster name and network from url path
   862  // This is needed when webconfig config runs on a different cluster than webhook
   863  func overwriteClusterInfo(pod *corev1.Pod, params InjectionParameters) {
   864  	c := FindSidecar(pod)
   865  	if c == nil {
   866  		return
   867  	}
   868  	if len(params.proxyEnvs) > 0 {
   869  		log.Debugf("Updating cluster envs based on inject url: %s\n", params.proxyEnvs)
   870  		updateClusterEnvs(c, params.proxyEnvs)
   871  	}
   872  }
   873  
   874  func updateClusterEnvs(container *corev1.Container, newKVs map[string]string) {
   875  	envVars := make([]corev1.EnvVar, 0)
   876  
   877  	for _, env := range container.Env {
   878  		if _, found := newKVs[env.Name]; !found {
   879  			envVars = append(envVars, env)
   880  		}
   881  	}
   882  
   883  	keys := make([]string, 0, len(newKVs))
   884  	for key := range newKVs {
   885  		keys = append(keys, key)
   886  	}
   887  	sort.Strings(keys)
   888  	for _, key := range keys {
   889  		val := newKVs[key]
   890  		envVars = append(envVars, corev1.EnvVar{Name: key, Value: val, ValueFrom: nil})
   891  	}
   892  	container.Env = envVars
   893  }
   894  
   895  // GetProxyIDs returns the UID and GID to be used in the RunAsUser and RunAsGroup fields in the template
   896  // Inspects the namespace metadata for hints and fallbacks to the usual value of 1337.
   897  func GetProxyIDs(namespace *corev1.Namespace) (uid int64, gid int64) {
   898  	uid = constants.DefaultProxyUIDInt
   899  	gid = constants.DefaultProxyUIDInt
   900  
   901  	if namespace == nil {
   902  		return
   903  	}
   904  
   905  	// Check for OpenShift specifics and returns the max number in the range specified in the namespace annotation
   906  	if _, uidMax, err := getPreallocatedUIDRange(namespace); err == nil {
   907  		uid = *uidMax
   908  	}
   909  	if groups, err := getPreallocatedSupplementalGroups(namespace); err == nil && len(groups) > 0 {
   910  		gid = groups[0].Max
   911  	}
   912  
   913  	return
   914  }