istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/inject/webhook.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  	"crypto/sha256"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"net/http"
    23  	"os"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  	"text/template"
    28  	"time"
    29  
    30  	"github.com/prometheus/prometheus/util/strutil"
    31  	"gomodules.xyz/jsonpatch/v2"
    32  	admissionv1 "k8s.io/api/admission/v1"
    33  	kubeApiAdmissionv1beta1 "k8s.io/api/admission/v1beta1"
    34  	corev1 "k8s.io/api/core/v1"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/runtime/serializer"
    38  	kjson "k8s.io/apimachinery/pkg/runtime/serializer/json"
    39  	"k8s.io/apimachinery/pkg/types"
    40  	"k8s.io/apimachinery/pkg/util/mergepatch"
    41  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    42  	"sigs.k8s.io/yaml"
    43  
    44  	"istio.io/api/annotation"
    45  	"istio.io/api/label"
    46  	meshconfig "istio.io/api/mesh/v1alpha1"
    47  	opconfig "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
    48  	"istio.io/istio/pilot/cmd/pilot-agent/status"
    49  	"istio.io/istio/pilot/pkg/model"
    50  	"istio.io/istio/pkg/cluster"
    51  	"istio.io/istio/pkg/config/constants"
    52  	"istio.io/istio/pkg/config/mesh"
    53  	"istio.io/istio/pkg/kube"
    54  	"istio.io/istio/pkg/kube/kubetypes"
    55  	"istio.io/istio/pkg/kube/multicluster"
    56  	"istio.io/istio/pkg/log"
    57  	"istio.io/istio/pkg/platform"
    58  	"istio.io/istio/pkg/slices"
    59  	"istio.io/istio/pkg/util/protomarshal"
    60  	"istio.io/istio/pkg/util/sets"
    61  	iptablesconstants "istio.io/istio/tools/istio-iptables/pkg/constants"
    62  )
    63  
    64  var (
    65  	runtimeScheme     = runtime.NewScheme()
    66  	codecs            = serializer.NewCodecFactory(runtimeScheme)
    67  	deserializer      = codecs.UniversalDeserializer()
    68  	jsonSerializer    = kjson.NewSerializerWithOptions(kjson.DefaultMetaFactory, runtimeScheme, runtimeScheme, kjson.SerializerOptions{})
    69  	URLParameterToEnv = map[string]string{
    70  		"cluster": "ISTIO_META_CLUSTER_ID",
    71  		"net":     "ISTIO_META_NETWORK",
    72  	}
    73  )
    74  
    75  func init() {
    76  	_ = corev1.AddToScheme(runtimeScheme)
    77  	_ = admissionv1.AddToScheme(runtimeScheme)
    78  	_ = kubeApiAdmissionv1beta1.AddToScheme(runtimeScheme)
    79  }
    80  
    81  const (
    82  	// prometheus will convert annotation to this format
    83  	// `prometheus.io/scrape` `prometheus.io.scrape` `prometheus-io/scrape` have the same meaning in Prometheus
    84  	// for more details, please checkout [here](https://github.com/prometheus/prometheus/blob/71a0f42331566a8849863d77078083edbb0b3bc4/util/strutil/strconv.go#L40)
    85  	prometheusScrapeAnnotation = "prometheus_io_scrape"
    86  	prometheusPortAnnotation   = "prometheus_io_port"
    87  	prometheusPathAnnotation   = "prometheus_io_path"
    88  
    89  	watchDebounceDelay = 100 * time.Millisecond
    90  )
    91  
    92  const (
    93  	// InitContainers is the name of the property in k8s pod spec
    94  	InitContainers = "initContainers"
    95  
    96  	// Containers is the name of the property in k8s pod spec
    97  	Containers = "containers"
    98  )
    99  
   100  type WebhookConfig struct {
   101  	Templates  Templates
   102  	Values     ValuesConfig
   103  	MeshConfig *meshconfig.MeshConfig
   104  }
   105  
   106  // Webhook implements a mutating webhook for automatic proxy injection.
   107  type Webhook struct {
   108  	mu           sync.RWMutex
   109  	Config       *Config
   110  	meshConfig   *meshconfig.MeshConfig
   111  	valuesConfig ValuesConfig
   112  	namespaces   *multicluster.KclientComponent[*corev1.Namespace]
   113  
   114  	// please do not call SetHandler() on this watcher, instead us MultiCast.AddHandler()
   115  	watcher   Watcher
   116  	MultiCast *WatcherMulticast
   117  
   118  	env      *model.Environment
   119  	revision string
   120  }
   121  
   122  func (wh *Webhook) GetConfig() WebhookConfig {
   123  	wh.mu.RLock()
   124  	defer wh.mu.RUnlock()
   125  	return WebhookConfig{
   126  		Templates:  wh.Config.Templates,
   127  		Values:     wh.valuesConfig,
   128  		MeshConfig: wh.meshConfig,
   129  	}
   130  }
   131  
   132  // ParsedContainers holds the unmarshalled containers and initContainers
   133  type ParsedContainers struct {
   134  	Containers     []corev1.Container `json:"containers,omitempty"`
   135  	InitContainers []corev1.Container `json:"initContainers,omitempty"`
   136  }
   137  
   138  func (p ParsedContainers) AllContainers() []corev1.Container {
   139  	return append(slices.Clone(p.Containers), p.InitContainers...)
   140  }
   141  
   142  // nolint directives: interfacer
   143  func loadConfig(injectFile, valuesFile string) (*Config, string, error) {
   144  	data, err := os.ReadFile(injectFile)
   145  	if err != nil {
   146  		return nil, "", err
   147  	}
   148  	var c *Config
   149  	if c, err = unmarshalConfig(data); err != nil {
   150  		log.Warnf("Failed to parse injectFile %s", string(data))
   151  		return nil, "", err
   152  	}
   153  
   154  	valuesConfig, err := os.ReadFile(valuesFile)
   155  	if err != nil {
   156  		return nil, "", err
   157  	}
   158  	return c, string(valuesConfig), nil
   159  }
   160  
   161  func unmarshalConfig(data []byte) (*Config, error) {
   162  	c, err := UnmarshalConfig(data)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	log.Debugf("New inject configuration: sha256sum %x", sha256.Sum256(data))
   168  	log.Debugf("Policy: %v", c.Policy)
   169  	log.Debugf("AlwaysInjectSelector: %v", c.AlwaysInjectSelector)
   170  	log.Debugf("NeverInjectSelector: %v", c.NeverInjectSelector)
   171  	log.Debugf("Templates: %v", c.RawTemplates)
   172  	return &c, nil
   173  }
   174  
   175  // WebhookParameters configures parameters for the sidecar injection
   176  // webhook.
   177  type WebhookParameters struct {
   178  	// Watcher watches the sidecar injection configuration.
   179  	Watcher Watcher
   180  
   181  	// Port is the webhook port, e.g. typically 443 for https.
   182  	// This is mainly used for tests. Webhook runs on the port started by Istiod.
   183  	Port int
   184  
   185  	Env *model.Environment
   186  
   187  	// Use an existing mux instead of creating our own.
   188  	Mux *http.ServeMux
   189  
   190  	// The istio.io/rev this injector is responsible for
   191  	Revision string
   192  
   193  	// MultiCluster is used to access namespaces across clusters
   194  	MultiCluster multicluster.ComponentBuilder
   195  }
   196  
   197  // NewWebhook creates a new instance of a mutating webhook for automatic sidecar injection.
   198  func NewWebhook(p WebhookParameters) (*Webhook, error) {
   199  	if p.Mux == nil {
   200  		return nil, errors.New("expected mux to be passed, but was not passed")
   201  	}
   202  
   203  	wh := &Webhook{
   204  		watcher:    p.Watcher,
   205  		meshConfig: p.Env.Mesh(),
   206  		env:        p.Env,
   207  		revision:   p.Revision,
   208  	}
   209  
   210  	if p.MultiCluster != nil {
   211  		if platform.IsOpenShift() {
   212  			wh.namespaces = multicluster.BuildMultiClusterKclientComponent[*corev1.Namespace](p.MultiCluster, kubetypes.Filter{})
   213  		}
   214  	}
   215  
   216  	mc := NewMulticast(p.Watcher, wh.GetConfig)
   217  	mc.AddHandler(wh.updateConfig)
   218  	wh.MultiCast = mc
   219  	sidecarConfig, valuesConfig, err := p.Watcher.Get()
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	if err := wh.updateConfig(sidecarConfig, valuesConfig); err != nil {
   224  		log.Errorf("failed to process webhook config: %v", err)
   225  	}
   226  
   227  	p.Mux.HandleFunc("/inject", wh.serveInject)
   228  	p.Mux.HandleFunc("/inject/", wh.serveInject)
   229  
   230  	p.Env.Watcher.AddMeshHandler(func() {
   231  		wh.mu.Lock()
   232  		wh.meshConfig = p.Env.Mesh()
   233  		wh.mu.Unlock()
   234  	})
   235  
   236  	return wh, nil
   237  }
   238  
   239  // Run implements the webhook server
   240  func (wh *Webhook) Run(stop <-chan struct{}) {
   241  	go wh.watcher.Run(stop)
   242  }
   243  
   244  func (wh *Webhook) updateConfig(sidecarConfig *Config, valuesConfig string) error {
   245  	wh.mu.Lock()
   246  	defer wh.mu.Unlock()
   247  	wh.Config = sidecarConfig
   248  	vc, err := NewValuesConfig(valuesConfig)
   249  	if err != nil {
   250  		return err
   251  	}
   252  	wh.valuesConfig = vc
   253  	return nil
   254  }
   255  
   256  type ContainerReorder int
   257  
   258  const (
   259  	MoveFirst ContainerReorder = iota
   260  	MoveLast
   261  	Remove
   262  )
   263  
   264  func moveContainer(from, to []corev1.Container, name string) ([]corev1.Container, []corev1.Container) {
   265  	var container *corev1.Container
   266  	for i, c := range from {
   267  		c := c
   268  		if from[i].Name == name {
   269  			from = slices.Delete(from, i)
   270  			container = &c
   271  			break
   272  		}
   273  	}
   274  	if container != nil {
   275  		to = append(to, *container)
   276  	}
   277  	return from, to
   278  }
   279  
   280  func modifyContainers(cl []corev1.Container, name string, modifier ContainerReorder) []corev1.Container {
   281  	containers := []corev1.Container{}
   282  	var match *corev1.Container
   283  	for _, c := range cl {
   284  		c := c
   285  		if c.Name != name {
   286  			containers = append(containers, c)
   287  		} else {
   288  			match = &c
   289  		}
   290  	}
   291  	if match == nil {
   292  		return containers
   293  	}
   294  	switch modifier {
   295  	case MoveFirst:
   296  		return append([]corev1.Container{*match}, containers...)
   297  	case MoveLast:
   298  		return append(containers, *match)
   299  	case Remove:
   300  		return containers
   301  	default:
   302  		return cl
   303  	}
   304  }
   305  
   306  func hasContainer(cl []corev1.Container, name string) bool {
   307  	for _, c := range cl {
   308  		if c.Name == name {
   309  			return true
   310  		}
   311  	}
   312  	return false
   313  }
   314  
   315  func enablePrometheusMerge(mesh *meshconfig.MeshConfig, anno map[string]string) bool {
   316  	// If annotation is present, we look there first
   317  	if val, f := anno[annotation.PrometheusMergeMetrics.Name]; f {
   318  		bval, err := strconv.ParseBool(val)
   319  		if err != nil {
   320  			// This shouldn't happen since we validate earlier in the code
   321  			log.Warnf("invalid annotation %v=%v", annotation.PrometheusMergeMetrics.Name, bval)
   322  		} else {
   323  			return bval
   324  		}
   325  	}
   326  	// If mesh config setting is present, use that
   327  	if mesh.GetEnablePrometheusMerge() != nil {
   328  		return mesh.GetEnablePrometheusMerge().Value
   329  	}
   330  	// Otherwise, we default to enable
   331  	return true
   332  }
   333  
   334  func toAdmissionResponse(err error) *kube.AdmissionResponse {
   335  	return &kube.AdmissionResponse{Result: &metav1.Status{Message: err.Error()}}
   336  }
   337  
   338  func ParseTemplates(tmpls RawTemplates) (Templates, error) {
   339  	ret := make(Templates, len(tmpls))
   340  	for k, t := range tmpls {
   341  		p, err := parseDryTemplate(t, InjectionFuncmap)
   342  		if err != nil {
   343  			return nil, err
   344  		}
   345  		ret[k] = p
   346  	}
   347  	return ret, nil
   348  }
   349  
   350  type ValuesConfig struct {
   351  	raw      string
   352  	asStruct *opconfig.Values
   353  	asMap    map[string]any
   354  }
   355  
   356  func (v ValuesConfig) Struct() *opconfig.Values {
   357  	return v.asStruct
   358  }
   359  
   360  func (v ValuesConfig) Map() map[string]any {
   361  	return v.asMap
   362  }
   363  
   364  func NewValuesConfig(v string) (ValuesConfig, error) {
   365  	c := ValuesConfig{raw: v}
   366  	valuesStruct := &opconfig.Values{}
   367  	if err := protomarshal.ApplyYAML(v, valuesStruct); err != nil {
   368  		return c, fmt.Errorf("could not parse configuration values: %v", err)
   369  	}
   370  	c.asStruct = valuesStruct
   371  
   372  	values := map[string]any{}
   373  	if err := yaml.Unmarshal([]byte(v), &values); err != nil {
   374  		return c, fmt.Errorf("could not parse configuration values: %v", err)
   375  	}
   376  	c.asMap = values
   377  	return c, nil
   378  }
   379  
   380  type InjectionParameters struct {
   381  	pod                 *corev1.Pod
   382  	deployMeta          types.NamespacedName
   383  	namespace           *corev1.Namespace
   384  	typeMeta            metav1.TypeMeta
   385  	templates           map[string]*template.Template
   386  	defaultTemplate     []string
   387  	aliases             map[string][]string
   388  	meshConfig          *meshconfig.MeshConfig
   389  	proxyConfig         *meshconfig.ProxyConfig
   390  	valuesConfig        ValuesConfig
   391  	revision            string
   392  	proxyEnvs           map[string]string
   393  	injectedAnnotations map[string]string
   394  }
   395  
   396  func checkPreconditions(params InjectionParameters) {
   397  	spec := params.pod.Spec
   398  	metadata := params.pod.ObjectMeta
   399  	// If DNSPolicy is not ClusterFirst, the Envoy sidecar may not able to connect to Istio Pilot.
   400  	if spec.DNSPolicy != "" && spec.DNSPolicy != corev1.DNSClusterFirst {
   401  		podName := potentialPodName(metadata)
   402  		log.Warnf("%q's DNSPolicy is not %q. The Envoy sidecar may not able to connect to Istio Pilot",
   403  			metadata.Namespace+"/"+podName, corev1.DNSClusterFirst)
   404  	}
   405  }
   406  
   407  func getInjectionStatus(podSpec corev1.PodSpec, revision string) string {
   408  	stat := &SidecarInjectionStatus{}
   409  	for _, c := range podSpec.InitContainers {
   410  		stat.InitContainers = append(stat.InitContainers, c.Name)
   411  	}
   412  	for _, c := range podSpec.Containers {
   413  		stat.Containers = append(stat.Containers, c.Name)
   414  	}
   415  	for _, c := range podSpec.Volumes {
   416  		stat.Volumes = append(stat.Volumes, c.Name)
   417  	}
   418  	for _, c := range podSpec.ImagePullSecrets {
   419  		stat.ImagePullSecrets = append(stat.ImagePullSecrets, c.Name)
   420  	}
   421  	// Rather than setting istio.io/rev label on injected pods include them here in status annotation.
   422  	// This keeps us from overwriting the istio.io/rev label when using revision tags (i.e. istio.io/rev=<tag>).
   423  	if revision == "" {
   424  		revision = "default"
   425  	}
   426  	stat.Revision = revision
   427  	statusAnnotationValue, err := json.Marshal(stat)
   428  	if err != nil {
   429  		return "{}"
   430  	}
   431  	return string(statusAnnotationValue)
   432  }
   433  
   434  // injectPod is the core of the injection logic. This takes a pod and injection
   435  // template, as well as some inputs to the injection template, and produces a
   436  // JSON patch.
   437  //
   438  // In the webhook, we will receive a Pod directly from Kubernetes, and return the
   439  // patch directly; Kubernetes will take care of applying the patch.
   440  //
   441  // For kube-inject, we will parse out a Pod from YAML (which may involve
   442  // extraction from higher level types like Deployment), then apply the patch
   443  // locally.
   444  //
   445  // The injection logic works by first applying the rendered injection template on
   446  // top of the input pod This is done using a Strategic Patch Merge
   447  // (https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md)
   448  // Currently only a single template is supported, although in the future the template to use will be configurable
   449  // and multiple templates will be supported by applying them in successive order.
   450  //
   451  // In addition to the plain templating, there is some post processing done to
   452  // handle cases that cannot feasibly be covered in the template, such as
   453  // re-ordering pods, rewriting readiness probes, etc.
   454  func injectPod(req InjectionParameters) ([]byte, error) {
   455  	checkPreconditions(req)
   456  
   457  	// The patch will be built relative to the initial pod, capture its current state
   458  	originalPodSpec, err := json.Marshal(req.pod)
   459  	if err != nil {
   460  		return nil, err
   461  	}
   462  
   463  	// Run the injection template, giving us a partial pod spec
   464  	mergedPod, injectedPodData, err := RunTemplate(req)
   465  	if err != nil {
   466  		return nil, fmt.Errorf("failed to run injection template: %v", err)
   467  	}
   468  
   469  	mergedPod, err = reapplyOverwrittenContainers(mergedPod, req.pod, injectedPodData, req.proxyConfig)
   470  	if err != nil {
   471  		return nil, fmt.Errorf("failed to re apply container: %v", err)
   472  	}
   473  
   474  	// Apply some additional transformations to the pod
   475  	if err := postProcessPod(mergedPod, *injectedPodData, req); err != nil {
   476  		return nil, fmt.Errorf("failed to process pod: %v", err)
   477  	}
   478  
   479  	patch, err := createPatch(mergedPod, originalPodSpec)
   480  	if err != nil {
   481  		return nil, fmt.Errorf("failed to create patch: %v", err)
   482  	}
   483  
   484  	log.Debugf("AdmissionResponse: patch=%v\n", string(patch))
   485  	return patch, nil
   486  }
   487  
   488  // reapplyOverwrittenContainers enables users to provide container level overrides for settings in the injection template
   489  // * originalPod: the pod before injection. If needed, we will apply some configurations from this pod on top of the final pod
   490  // * templatePod: the rendered injection template. This is needed only to see what containers we injected
   491  // * finalPod: the current result of injection, roughly equivalent to the merging of originalPod and templatePod
   492  // There are essentially three cases we cover here:
   493  //  1. There is no overlap in containers in original and template pod. We will do nothing.
   494  //  2. There is an overlap (ie, both define istio-proxy), but that is because the pod is being re-injected.
   495  //     In this case we do nothing, since we want to apply the new settings
   496  //  3. There is an overlap. We will re-apply the original container.
   497  //
   498  // Where "overlap" is a container defined in both the original and template pod. Typically, this would mean
   499  // the user has defined an `istio-proxy` container in their own pod spec.
   500  func reapplyOverwrittenContainers(finalPod *corev1.Pod, originalPod *corev1.Pod, templatePod *corev1.Pod,
   501  	proxyConfig *meshconfig.ProxyConfig,
   502  ) (*corev1.Pod, error) {
   503  	overrides := ParsedContainers{}
   504  	existingOverrides := ParsedContainers{}
   505  	if annotationOverrides, f := originalPod.Annotations[annotation.ProxyOverrides.Name]; f {
   506  		if err := json.Unmarshal([]byte(annotationOverrides), &existingOverrides); err != nil {
   507  			return nil, err
   508  		}
   509  	}
   510  	parsedInjectedStatus := ParsedContainers{}
   511  	status, alreadyInjected := originalPod.Annotations[annotation.SidecarStatus.Name]
   512  	if alreadyInjected {
   513  		parsedInjectedStatus = parseStatus(status)
   514  	}
   515  	for _, c := range templatePod.Spec.Containers {
   516  		// sidecarStatus annotation is added on the pod by webhook. We should use new container template
   517  		// instead of restoring what may be previously injected. Doing this ensures we are correctly calculating
   518  		// env variables like ISTIO_META_APP_CONTAINERS and ISTIO_META_POD_PORTS.
   519  		if match := FindContainer(c.Name, parsedInjectedStatus.Containers); match != nil {
   520  			continue
   521  		}
   522  		match := FindContainer(c.Name, existingOverrides.Containers)
   523  		if match == nil {
   524  			match = FindContainer(c.Name, originalPod.Spec.Containers)
   525  		}
   526  		if match == nil {
   527  			continue
   528  		}
   529  		overlay := *match.DeepCopy()
   530  		if overlay.Image == AutoImage {
   531  			overlay.Image = ""
   532  		}
   533  
   534  		overrides.Containers = append(overrides.Containers, overlay)
   535  		newMergedPod, err := applyContainer(finalPod, overlay)
   536  		if err != nil {
   537  			return nil, fmt.Errorf("failed to apply sidecar container: %v", err)
   538  		}
   539  		finalPod = newMergedPod
   540  	}
   541  	for _, c := range templatePod.Spec.InitContainers {
   542  		if match := FindContainer(c.Name, parsedInjectedStatus.InitContainers); match != nil {
   543  			continue
   544  		}
   545  		match := FindContainer(c.Name, existingOverrides.InitContainers)
   546  		if match == nil {
   547  			match = FindContainerFromPod(c.Name, originalPod)
   548  		}
   549  		if match == nil {
   550  			continue
   551  		}
   552  		overlay := *match.DeepCopy()
   553  		if overlay.Image == AutoImage {
   554  			overlay.Image = ""
   555  		}
   556  
   557  		overrides.InitContainers = append(overrides.InitContainers, overlay)
   558  		newMergedPod, err := applyInitContainer(finalPod, overlay)
   559  		if err != nil {
   560  			return nil, fmt.Errorf("failed to apply sidecar init container: %v", err)
   561  		}
   562  		finalPod = newMergedPod
   563  	}
   564  
   565  	if !alreadyInjected && (len(overrides.Containers) > 0 || len(overrides.InitContainers) > 0) {
   566  		// We found any overrides. Put them in the pod annotation so we can re-apply them on re-injection
   567  		js, err := json.Marshal(overrides)
   568  		if err != nil {
   569  			return nil, err
   570  		}
   571  		if finalPod.Annotations == nil {
   572  			finalPod.Annotations = map[string]string{}
   573  		}
   574  		finalPod.Annotations[annotation.ProxyOverrides.Name] = string(js)
   575  	}
   576  
   577  	adjustInitContainerUser(finalPod, originalPod, proxyConfig)
   578  
   579  	return finalPod, nil
   580  }
   581  
   582  // adjustInitContainerUser adjusts the RunAsUser/Group fields and iptables parameter "-u <uid>"
   583  // in the init/validation container so that they match the value of SecurityContext.RunAsUser/Group
   584  // when it is present in the custom istio-proxy container supplied by the user.
   585  func adjustInitContainerUser(finalPod *corev1.Pod, originalPod *corev1.Pod, proxyConfig *meshconfig.ProxyConfig) {
   586  	userContainer := FindSidecar(originalPod)
   587  	if userContainer == nil {
   588  		// if user doesn't override the istio-proxy container, there's nothing to do
   589  		return
   590  	}
   591  
   592  	if userContainer.SecurityContext == nil || (userContainer.SecurityContext.RunAsUser == nil && userContainer.SecurityContext.RunAsGroup == nil) {
   593  		// if user doesn't override SecurityContext.RunAsUser/Group, there's nothing to do
   594  		return
   595  	}
   596  
   597  	// Locate the istio-init or istio-validation container
   598  	var initContainer *corev1.Container
   599  	for _, name := range []string{InitContainerName, ValidationContainerName} {
   600  		if container := FindContainer(name, finalPod.Spec.InitContainers); container != nil {
   601  			initContainer = container
   602  			break
   603  		}
   604  	}
   605  	if initContainer == nil {
   606  		// should not happen
   607  		log.Warn("Could not find either istio-init or istio-validation container")
   608  		return
   609  	}
   610  
   611  	// Overriding RunAsUser is now allowed in TPROXY mode, it must always run with uid=0
   612  	tproxy := false
   613  	if proxyConfig.InterceptionMode == meshconfig.ProxyConfig_TPROXY {
   614  		tproxy = true
   615  	} else if mode, found := finalPod.Annotations[annotation.SidecarInterceptionMode.Name]; found && mode == iptablesconstants.TPROXY {
   616  		tproxy = true
   617  	}
   618  
   619  	// RunAsUser cannot be overridden (ie, must remain 0) in TPROXY mode
   620  	if tproxy && userContainer.SecurityContext.RunAsUser != nil {
   621  		sidecar := FindSidecar(finalPod)
   622  		if sidecar == nil {
   623  			// Should not happen
   624  			log.Warn("Could not find the istio-proxy container")
   625  			return
   626  		}
   627  		*sidecar.SecurityContext.RunAsUser = 0
   628  	}
   629  
   630  	// Make sure the validation container runs with the same uid/gid as the proxy (init container is untouched, it must run with 0)
   631  	if !tproxy && initContainer.Name == ValidationContainerName {
   632  		if initContainer.SecurityContext == nil {
   633  			initContainer.SecurityContext = &corev1.SecurityContext{}
   634  		}
   635  		if userContainer.SecurityContext.RunAsUser != nil {
   636  			initContainer.SecurityContext.RunAsUser = userContainer.SecurityContext.RunAsUser
   637  		}
   638  		if userContainer.SecurityContext.RunAsGroup != nil {
   639  			initContainer.SecurityContext.RunAsGroup = userContainer.SecurityContext.RunAsGroup
   640  		}
   641  	}
   642  
   643  	// Find the "-u <uid>" parameter in the init container and replace it with the userid from SecurityContext.RunAsUser
   644  	// but only if it's not 0. iptables --uid-owner argument must not be 0.
   645  	if userContainer.SecurityContext.RunAsUser == nil || *userContainer.SecurityContext.RunAsUser == 0 {
   646  		return
   647  	}
   648  	for i := range initContainer.Args {
   649  		if initContainer.Args[i] == "-u" {
   650  			initContainer.Args[i+1] = fmt.Sprintf("%d", *userContainer.SecurityContext.RunAsUser)
   651  			return
   652  		}
   653  	}
   654  }
   655  
   656  // parseStatus extracts containers from injected SidecarStatus annotation
   657  func parseStatus(status string) ParsedContainers {
   658  	parsedContainers := ParsedContainers{}
   659  	var unMarshalledStatus map[string]interface{}
   660  	if err := json.Unmarshal([]byte(status), &unMarshalledStatus); err != nil {
   661  		log.Errorf("Failed to unmarshal %s : %v", annotation.SidecarStatus.Name, err)
   662  		return parsedContainers
   663  	}
   664  	parser := func(key string, obj map[string]interface{}) []corev1.Container {
   665  		out := make([]corev1.Container, 0)
   666  		if value, exist := obj[key]; exist && value != nil {
   667  			for _, v := range value.([]interface{}) {
   668  				out = append(out, corev1.Container{Name: v.(string)})
   669  			}
   670  		}
   671  		return out
   672  	}
   673  	parsedContainers.Containers = parser(Containers, unMarshalledStatus)
   674  	parsedContainers.InitContainers = parser(InitContainers, unMarshalledStatus)
   675  
   676  	return parsedContainers
   677  }
   678  
   679  // reinsertOverrides applies the containers listed in OverrideAnnotation to a pod. This is to achieve
   680  // idempotency by handling an edge case where an injection template is modifying a container already
   681  // present in the pod spec. In these cases, the logic to strip injected containers would remove the
   682  // original injected parts as well, leading to the templating logic being different (for example,
   683  // reading the .Spec.Containers field would be empty).
   684  func reinsertOverrides(pod *corev1.Pod) (*corev1.Pod, error) {
   685  	type podOverrides struct {
   686  		Containers     []corev1.Container `json:"containers,omitempty"`
   687  		InitContainers []corev1.Container `json:"initContainers,omitempty"`
   688  	}
   689  
   690  	existingOverrides := podOverrides{}
   691  	if annotationOverrides, f := pod.Annotations[annotation.ProxyOverrides.Name]; f {
   692  		if err := json.Unmarshal([]byte(annotationOverrides), &existingOverrides); err != nil {
   693  			return nil, err
   694  		}
   695  	}
   696  
   697  	pod = pod.DeepCopy()
   698  	for _, c := range existingOverrides.Containers {
   699  		match := FindContainer(c.Name, pod.Spec.Containers)
   700  		if match != nil {
   701  			continue
   702  		}
   703  		pod.Spec.Containers = append(pod.Spec.Containers, c)
   704  	}
   705  
   706  	for _, c := range existingOverrides.InitContainers {
   707  		match := FindContainer(c.Name, pod.Spec.InitContainers)
   708  		if match != nil {
   709  			continue
   710  		}
   711  		pod.Spec.InitContainers = append(pod.Spec.InitContainers, c)
   712  	}
   713  
   714  	return pod, nil
   715  }
   716  
   717  func createPatch(pod *corev1.Pod, original []byte) ([]byte, error) {
   718  	reinjected, err := json.Marshal(pod)
   719  	if err != nil {
   720  		return nil, err
   721  	}
   722  	p, err := jsonpatch.CreatePatch(original, reinjected)
   723  	if err != nil {
   724  		return nil, err
   725  	}
   726  	return json.Marshal(p)
   727  }
   728  
   729  // postProcessPod applies additionally transformations to the pod after merging with the injected template
   730  // This is generally things that cannot reasonably be added to the template
   731  func postProcessPod(pod *corev1.Pod, injectedPod corev1.Pod, req InjectionParameters) error {
   732  	if pod.Annotations == nil {
   733  		pod.Annotations = map[string]string{}
   734  	}
   735  	if pod.Labels == nil {
   736  		pod.Labels = map[string]string{}
   737  	}
   738  
   739  	overwriteClusterInfo(pod, req)
   740  
   741  	if err := applyPrometheusMerge(pod, req.meshConfig); err != nil {
   742  		return err
   743  	}
   744  
   745  	if err := applyRewrite(pod, req); err != nil {
   746  		return err
   747  	}
   748  
   749  	applyMetadata(pod, injectedPod, req)
   750  
   751  	if err := reorderPod(pod, req); err != nil {
   752  		return err
   753  	}
   754  
   755  	return nil
   756  }
   757  
   758  func applyMetadata(pod *corev1.Pod, injectedPodData corev1.Pod, req InjectionParameters) {
   759  	if nw, ok := req.proxyEnvs["ISTIO_META_NETWORK"]; ok {
   760  		pod.Labels[label.TopologyNetwork.Name] = nw
   761  	}
   762  	// Add all additional injected annotations. These are overridden if needed
   763  	pod.Annotations[annotation.SidecarStatus.Name] = getInjectionStatus(injectedPodData.Spec, req.revision)
   764  
   765  	// Deprecated; should be set directly in the template instead
   766  	for k, v := range req.injectedAnnotations {
   767  		pod.Annotations[k] = v
   768  	}
   769  }
   770  
   771  // reorderPod ensures containers are properly ordered after merging
   772  func reorderPod(pod *corev1.Pod, req InjectionParameters) error {
   773  	var merr error
   774  	mc := req.meshConfig
   775  	// Get copy of pod proxyconfig, to determine container ordering
   776  	if pca, f := req.pod.ObjectMeta.GetAnnotations()[annotation.ProxyConfig.Name]; f {
   777  		mc, merr = mesh.ApplyProxyConfig(pca, req.meshConfig)
   778  		if merr != nil {
   779  			return merr
   780  		}
   781  	}
   782  
   783  	// nolint: staticcheck
   784  	holdPod := mc.GetDefaultConfig().GetHoldApplicationUntilProxyStarts().GetValue() ||
   785  		req.valuesConfig.asStruct.GetGlobal().GetProxy().GetHoldApplicationUntilProxyStarts().GetValue()
   786  
   787  	proxyLocation := MoveLast
   788  	// If HoldApplicationUntilProxyStarts is set, reorder the proxy location
   789  	if holdPod {
   790  		proxyLocation = MoveFirst
   791  	}
   792  
   793  	// Proxy container should be last, unless HoldApplicationUntilProxyStarts is set
   794  	// This is to ensure `kubectl exec` and similar commands continue to default to the user's container
   795  	pod.Spec.Containers = modifyContainers(pod.Spec.Containers, ProxyContainerName, proxyLocation)
   796  
   797  	if hasContainer(pod.Spec.InitContainers, ProxyContainerName) {
   798  		// This is using native sidecar support in K8s.
   799  		// We want istio to be first in this case, so init containers are part of the mesh
   800  		// This is {istio-init/istio-validation} => proxy => rest.
   801  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, EnableCoreDumpName, MoveFirst)
   802  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, ProxyContainerName, MoveFirst)
   803  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, ValidationContainerName, MoveFirst)
   804  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, InitContainerName, MoveFirst)
   805  	} else {
   806  		// Else, we want iptables setup last so we do not blackhole init containers
   807  		// This is istio-validation => rest => istio-init (note: only one of istio-init or istio-validation should be present)
   808  		// Validation container must be first to block any user containers
   809  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, ValidationContainerName, MoveFirst)
   810  		// Init container must be last to allow any traffic to pass before iptables is setup
   811  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, InitContainerName, MoveLast)
   812  		pod.Spec.InitContainers = modifyContainers(pod.Spec.InitContainers, EnableCoreDumpName, MoveLast)
   813  	}
   814  
   815  	return nil
   816  }
   817  
   818  func applyRewrite(pod *corev1.Pod, req InjectionParameters) error {
   819  	sidecar := FindSidecar(pod)
   820  	if sidecar == nil {
   821  		return nil
   822  	}
   823  
   824  	rewrite := ShouldRewriteAppHTTPProbers(pod.Annotations, req.valuesConfig.asStruct.GetSidecarInjectorWebhook().GetRewriteAppHTTPProbe().GetValue())
   825  	// We don't have to escape json encoding here when using golang libraries.
   826  	if rewrite {
   827  		if prober := DumpAppProbers(pod, req.meshConfig.GetDefaultConfig().GetStatusPort()); prober != "" {
   828  			// If sidecar.istio.io/status is not present then append instead of merge.
   829  			_, previouslyInjected := pod.Annotations[annotation.SidecarStatus.Name]
   830  			sidecar.Env = mergeOrAppendProbers(previouslyInjected, sidecar.Env, prober)
   831  		}
   832  		patchRewriteProbe(pod.Annotations, pod, req.meshConfig.GetDefaultConfig().GetStatusPort())
   833  	}
   834  	return nil
   835  }
   836  
   837  // mergeOrAppendProbers ensures that if sidecar has existing ISTIO_KUBE_APP_PROBERS,
   838  // then probers should be merged.
   839  func mergeOrAppendProbers(previouslyInjected bool, envVars []corev1.EnvVar, newProbers string) []corev1.EnvVar {
   840  	if !previouslyInjected {
   841  		return append(envVars, corev1.EnvVar{Name: status.KubeAppProberEnvName, Value: newProbers})
   842  	}
   843  	for idx, env := range envVars {
   844  		if env.Name == status.KubeAppProberEnvName {
   845  			var existingKubeAppProber KubeAppProbers
   846  			err := json.Unmarshal([]byte(env.Value), &existingKubeAppProber)
   847  			if err != nil {
   848  				log.Errorf("failed to unmarshal existing kubeAppProbers %v", err)
   849  				return envVars
   850  			}
   851  			var newKubeAppProber KubeAppProbers
   852  			err = json.Unmarshal([]byte(newProbers), &newKubeAppProber)
   853  			if err != nil {
   854  				log.Errorf("failed to unmarshal new kubeAppProbers %v", err)
   855  				return envVars
   856  			}
   857  			for k, v := range existingKubeAppProber {
   858  				// merge old and new probers.
   859  				newKubeAppProber[k] = v
   860  			}
   861  			marshalledKubeAppProber, err := json.Marshal(newKubeAppProber)
   862  			if err != nil {
   863  				log.Errorf("failed to serialize the merged app prober config %v", err)
   864  				return envVars
   865  			}
   866  			// replace old env var with new value.
   867  			envVars[idx] = corev1.EnvVar{Name: status.KubeAppProberEnvName, Value: string(marshalledKubeAppProber)}
   868  			return envVars
   869  		}
   870  	}
   871  	return envVars
   872  }
   873  
   874  var emptyScrape = status.PrometheusScrapeConfiguration{}
   875  
   876  // applyPrometheusMerge configures prometheus scraping annotations for the "metrics merge" feature.
   877  // This moves the current prometheus.io annotations into an environment variable and replaces them
   878  // pointing to the agent.
   879  func applyPrometheusMerge(pod *corev1.Pod, mesh *meshconfig.MeshConfig) error {
   880  	if getPrometheusScrape(pod) &&
   881  		enablePrometheusMerge(mesh, pod.ObjectMeta.Annotations) {
   882  		targetPort := strconv.Itoa(int(mesh.GetDefaultConfig().GetStatusPort()))
   883  		if cur, f := getPrometheusPort(pod); f {
   884  			// We have already set the port, assume user is controlling this or, more likely, re-injected
   885  			// the pod.
   886  			if cur == targetPort {
   887  				return nil
   888  			}
   889  		}
   890  		scrape := getPrometheusScrapeConfiguration(pod)
   891  		sidecar := FindSidecar(pod)
   892  		if sidecar != nil && scrape != emptyScrape {
   893  			by, err := json.Marshal(scrape)
   894  			if err != nil {
   895  				return err
   896  			}
   897  			sidecar.Env = append(sidecar.Env, corev1.EnvVar{Name: status.PrometheusScrapingConfig.Name, Value: string(by)})
   898  		}
   899  		if pod.Annotations == nil {
   900  			pod.Annotations = map[string]string{}
   901  		}
   902  		// if a user sets `prometheus/io/path: foo`, then we add `prometheus.io/path: /stats/prometheus`
   903  		// prometheus will pick a random one
   904  		// need to clear out all variants and then set ours
   905  		clearPrometheusAnnotations(pod)
   906  		pod.Annotations["prometheus.io/port"] = targetPort
   907  		pod.Annotations["prometheus.io/path"] = "/stats/prometheus"
   908  		pod.Annotations["prometheus.io/scrape"] = "true"
   909  		return nil
   910  	}
   911  
   912  	return nil
   913  }
   914  
   915  // getPrometheusScrape respect prometheus scrape config
   916  // not to doing prometheusMerge if this return false
   917  func getPrometheusScrape(pod *corev1.Pod) bool {
   918  	for k, val := range pod.Annotations {
   919  		if strutil.SanitizeLabelName(k) != prometheusScrapeAnnotation {
   920  			continue
   921  		}
   922  
   923  		if scrape, err := strconv.ParseBool(val); err == nil {
   924  			return scrape
   925  		}
   926  	}
   927  
   928  	return true
   929  }
   930  
   931  var prometheusAnnotations = sets.New(
   932  	prometheusPathAnnotation,
   933  	prometheusPortAnnotation,
   934  	prometheusScrapeAnnotation,
   935  )
   936  
   937  func clearPrometheusAnnotations(pod *corev1.Pod) {
   938  	needRemovedKeys := make([]string, 0, 2)
   939  	for k := range pod.Annotations {
   940  		anno := strutil.SanitizeLabelName(k)
   941  		if prometheusAnnotations.Contains(anno) {
   942  			needRemovedKeys = append(needRemovedKeys, k)
   943  		}
   944  	}
   945  
   946  	for _, k := range needRemovedKeys {
   947  		delete(pod.Annotations, k)
   948  	}
   949  }
   950  
   951  func getPrometheusScrapeConfiguration(pod *corev1.Pod) status.PrometheusScrapeConfiguration {
   952  	cfg := status.PrometheusScrapeConfiguration{}
   953  
   954  	for k, val := range pod.Annotations {
   955  		anno := strutil.SanitizeLabelName(k)
   956  		switch anno {
   957  		case prometheusPortAnnotation:
   958  			cfg.Port = val
   959  		case prometheusScrapeAnnotation:
   960  			cfg.Scrape = val
   961  		case prometheusPathAnnotation:
   962  			cfg.Path = val
   963  		}
   964  	}
   965  
   966  	return cfg
   967  }
   968  
   969  func getPrometheusPort(pod *corev1.Pod) (string, bool) {
   970  	for k, val := range pod.Annotations {
   971  		if strutil.SanitizeLabelName(k) != prometheusPortAnnotation {
   972  			continue
   973  		}
   974  
   975  		return val, true
   976  	}
   977  
   978  	return "", false
   979  }
   980  
   981  const (
   982  	// AutoImage is the special image name to indicate to the injector that we should use the injected image, and NOT override it
   983  	// This is necessary because image is a required field on container, so if a user defines an istio-proxy container
   984  	// with customizations they must set an image.
   985  	AutoImage = "auto"
   986  )
   987  
   988  // applyContainer merges a container spec on top of the provided pod
   989  func applyContainer(target *corev1.Pod, container corev1.Container) (*corev1.Pod, error) {
   990  	overlay := &corev1.Pod{Spec: corev1.PodSpec{Containers: []corev1.Container{container}}}
   991  
   992  	overlayJSON, err := json.Marshal(overlay)
   993  	if err != nil {
   994  		return nil, err
   995  	}
   996  
   997  	return applyOverlay(target, overlayJSON)
   998  }
   999  
  1000  // applyInitContainer merges a container spec on top of the provided pod as an init container
  1001  func applyInitContainer(target *corev1.Pod, container corev1.Container) (*corev1.Pod, error) {
  1002  	overlay := &corev1.Pod{Spec: corev1.PodSpec{
  1003  		// We need to set containers to empty, otherwise it will marshal as "null" and delete all containers
  1004  		Containers:     []corev1.Container{},
  1005  		InitContainers: []corev1.Container{container},
  1006  	}}
  1007  
  1008  	overlayJSON, err := json.Marshal(overlay)
  1009  	if err != nil {
  1010  		return nil, err
  1011  	}
  1012  
  1013  	return applyOverlay(target, overlayJSON)
  1014  }
  1015  
  1016  func patchHandleUnmarshal(j []byte, unmarshal func(data []byte, v any) error) (map[string]any, error) {
  1017  	if j == nil {
  1018  		j = []byte("{}")
  1019  	}
  1020  
  1021  	m := map[string]any{}
  1022  	err := unmarshal(j, &m)
  1023  	if err != nil {
  1024  		return nil, mergepatch.ErrBadJSONDoc
  1025  	}
  1026  	return m, nil
  1027  }
  1028  
  1029  // StrategicMergePatchYAML is a small fork of strategicpatch.StrategicMergePatch to allow YAML patches
  1030  // This avoids expensive conversion from YAML to JSON
  1031  func StrategicMergePatchYAML(originalJSON []byte, patchYAML []byte, dataStruct any) ([]byte, error) {
  1032  	schema, err := strategicpatch.NewPatchMetaFromStruct(dataStruct)
  1033  	if err != nil {
  1034  		return nil, err
  1035  	}
  1036  
  1037  	originalMap, err := patchHandleUnmarshal(originalJSON, json.Unmarshal)
  1038  	if err != nil {
  1039  		return nil, err
  1040  	}
  1041  	patchMap, err := patchHandleUnmarshal(patchYAML, func(data []byte, v any) error {
  1042  		return yaml.Unmarshal(data, v)
  1043  	})
  1044  	if err != nil {
  1045  		return nil, err
  1046  	}
  1047  
  1048  	result, err := strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, schema)
  1049  	if err != nil {
  1050  		return nil, err
  1051  	}
  1052  
  1053  	return json.Marshal(result)
  1054  }
  1055  
  1056  // applyContainer merges a pod spec, provided as JSON, on top of the provided pod
  1057  func applyOverlayYAML(target *corev1.Pod, overlayYAML []byte) (*corev1.Pod, error) {
  1058  	currentJSON, err := json.Marshal(target)
  1059  	if err != nil {
  1060  		return nil, err
  1061  	}
  1062  
  1063  	pod := corev1.Pod{}
  1064  	// Overlay the injected template onto the original podSpec
  1065  	patched, err := StrategicMergePatchYAML(currentJSON, overlayYAML, pod)
  1066  	if err != nil {
  1067  		return nil, fmt.Errorf("strategic merge: %v", err)
  1068  	}
  1069  
  1070  	if err := json.Unmarshal(patched, &pod); err != nil {
  1071  		return nil, fmt.Errorf("unmarshal patched pod: %v", err)
  1072  	}
  1073  	return &pod, nil
  1074  }
  1075  
  1076  // applyContainer merges a pod spec, provided as JSON, on top of the provided pod
  1077  func applyOverlay(target *corev1.Pod, overlayJSON []byte) (*corev1.Pod, error) {
  1078  	currentJSON, err := json.Marshal(target)
  1079  	if err != nil {
  1080  		return nil, err
  1081  	}
  1082  
  1083  	pod := corev1.Pod{}
  1084  	// Overlay the injected template onto the original podSpec
  1085  	patched, err := strategicpatch.StrategicMergePatch(currentJSON, overlayJSON, pod)
  1086  	if err != nil {
  1087  		return nil, fmt.Errorf("strategic merge: %v", err)
  1088  	}
  1089  
  1090  	if err := json.Unmarshal(patched, &pod); err != nil {
  1091  		return nil, fmt.Errorf("unmarshal patched pod: %v", err)
  1092  	}
  1093  	return &pod, nil
  1094  }
  1095  
  1096  func (wh *Webhook) inject(ar *kube.AdmissionReview, path string) *kube.AdmissionResponse {
  1097  	req := ar.Request
  1098  	var pod corev1.Pod
  1099  	if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
  1100  		handleError(fmt.Sprintf("Could not unmarshal raw object: %v %s", err,
  1101  			string(req.Object.Raw)))
  1102  		return toAdmissionResponse(err)
  1103  	}
  1104  	// Managed fields is sometimes extremely large, leading to excessive CPU time on patch generation
  1105  	// It does not impact the injection output at all, so we can just remove it.
  1106  	pod.ManagedFields = nil
  1107  
  1108  	// Deal with potential empty fields, e.g., when the pod is created by a deployment
  1109  	podName := potentialPodName(pod.ObjectMeta)
  1110  	if pod.ObjectMeta.Namespace == "" {
  1111  		pod.ObjectMeta.Namespace = req.Namespace
  1112  	}
  1113  	log.Infof("Sidecar injection request for %v/%v", req.Namespace, podName)
  1114  	log.Debugf("Object: %v", string(req.Object.Raw))
  1115  	log.Debugf("OldObject: %v", string(req.OldObject.Raw))
  1116  
  1117  	wh.mu.RLock()
  1118  	if !injectRequired(IgnoredNamespaces.UnsortedList(), wh.Config, &pod.Spec, pod.ObjectMeta) {
  1119  		log.Infof("Skipping %s/%s due to policy check", pod.ObjectMeta.Namespace, podName)
  1120  		totalSkippedInjections.Increment()
  1121  		wh.mu.RUnlock()
  1122  		return &kube.AdmissionResponse{
  1123  			Allowed: true,
  1124  		}
  1125  	}
  1126  
  1127  	proxyConfig := wh.env.GetProxyConfigOrDefault(pod.Namespace, pod.Labels, pod.Annotations, wh.meshConfig)
  1128  	deploy, typeMeta := kube.GetDeployMetaFromPod(&pod)
  1129  
  1130  	params := InjectionParameters{
  1131  		pod:                 &pod,
  1132  		deployMeta:          deploy,
  1133  		typeMeta:            typeMeta,
  1134  		templates:           wh.Config.Templates,
  1135  		defaultTemplate:     wh.Config.DefaultTemplates,
  1136  		aliases:             wh.Config.Aliases,
  1137  		meshConfig:          wh.meshConfig,
  1138  		proxyConfig:         proxyConfig,
  1139  		valuesConfig:        wh.valuesConfig,
  1140  		revision:            wh.revision,
  1141  		injectedAnnotations: wh.Config.InjectedAnnotations,
  1142  		proxyEnvs:           parseInjectEnvs(path),
  1143  	}
  1144  
  1145  	if platform.IsOpenShift() && wh.namespaces != nil {
  1146  		clusterID, _ := extractClusterAndNetwork(params)
  1147  		if clusterID == "" {
  1148  			clusterID = constants.DefaultClusterName
  1149  		}
  1150  		client := wh.namespaces.ForCluster(cluster.ID(clusterID))
  1151  		if client != nil {
  1152  			params.namespace = client.Get(pod.Namespace, "")
  1153  		} else {
  1154  			log.Warnf("unable to fetch namespace, failed to get client for %q", clusterID)
  1155  		}
  1156  	}
  1157  	wh.mu.RUnlock()
  1158  
  1159  	patchBytes, err := injectPod(params)
  1160  	if err != nil {
  1161  		handleError(fmt.Sprintf("Pod injection failed: %v", err))
  1162  		return toAdmissionResponse(err)
  1163  	}
  1164  
  1165  	reviewResponse := kube.AdmissionResponse{
  1166  		Allowed: true,
  1167  		Patch:   patchBytes,
  1168  		PatchType: func() *string {
  1169  			pt := "JSONPatch"
  1170  			return &pt
  1171  		}(),
  1172  	}
  1173  	totalSuccessfulInjections.Increment()
  1174  	return &reviewResponse
  1175  }
  1176  
  1177  func (wh *Webhook) serveInject(w http.ResponseWriter, r *http.Request) {
  1178  	totalInjections.Increment()
  1179  	t0 := time.Now()
  1180  	defer func() { injectionTime.Record(time.Since(t0).Seconds()) }()
  1181  	var body []byte
  1182  	if r.Body != nil {
  1183  		if data, err := kube.HTTPConfigReader(r); err == nil {
  1184  			body = data
  1185  		} else {
  1186  			http.Error(w, err.Error(), http.StatusBadRequest)
  1187  			return
  1188  		}
  1189  	}
  1190  	if len(body) == 0 {
  1191  		handleError("no body found")
  1192  		http.Error(w, "no body found", http.StatusBadRequest)
  1193  		return
  1194  	}
  1195  
  1196  	// verify the content type is accurate
  1197  	contentType := r.Header.Get("Content-Type")
  1198  	if contentType != "application/json" {
  1199  		handleError(fmt.Sprintf("contentType=%s, expect application/json", contentType))
  1200  		http.Error(w, "invalid Content-Type, want `application/json`", http.StatusUnsupportedMediaType)
  1201  		return
  1202  	}
  1203  
  1204  	path := ""
  1205  	if r.URL != nil {
  1206  		path = r.URL.Path
  1207  	}
  1208  
  1209  	var reviewResponse *kube.AdmissionResponse
  1210  	var obj runtime.Object
  1211  	var ar *kube.AdmissionReview
  1212  	if out, _, err := deserializer.Decode(body, nil, obj); err != nil {
  1213  		handleError(fmt.Sprintf("Could not decode body: %v", err))
  1214  		reviewResponse = toAdmissionResponse(err)
  1215  	} else {
  1216  		log.Debugf("AdmissionRequest for path=%s\n", path)
  1217  		ar, err = kube.AdmissionReviewKubeToAdapter(out)
  1218  		if err != nil {
  1219  			handleError(fmt.Sprintf("Could not decode object: %v", err))
  1220  			reviewResponse = toAdmissionResponse(err)
  1221  		} else {
  1222  			reviewResponse = wh.inject(ar, path)
  1223  		}
  1224  	}
  1225  
  1226  	response := kube.AdmissionReview{}
  1227  	response.Response = reviewResponse
  1228  	var responseKube runtime.Object
  1229  	var apiVersion string
  1230  	if ar != nil {
  1231  		apiVersion = ar.APIVersion
  1232  		response.TypeMeta = ar.TypeMeta
  1233  		if response.Response != nil {
  1234  			if ar.Request != nil {
  1235  				response.Response.UID = ar.Request.UID
  1236  			}
  1237  		}
  1238  	}
  1239  	responseKube = kube.AdmissionReviewAdapterToKube(&response, apiVersion)
  1240  	resp, err := json.Marshal(responseKube)
  1241  	if err != nil {
  1242  		log.Errorf("Could not encode response: %v", err)
  1243  		http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
  1244  		return
  1245  	}
  1246  	if _, err := w.Write(resp); err != nil {
  1247  		log.Errorf("Could not write response: %v", err)
  1248  		http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
  1249  	}
  1250  }
  1251  
  1252  // parseInjectEnvs parse new envs from inject url path. format: /inject/k1/v1/k2/v2
  1253  // slash characters in values must be replaced by --slash-- (e.g. /inject/k1/abc--slash--def/k2/v2).
  1254  func parseInjectEnvs(path string) map[string]string {
  1255  	path = strings.TrimSuffix(path, "/")
  1256  	res := func(path string) []string {
  1257  		parts := strings.SplitN(path, "/", 3)
  1258  		var newRes []string
  1259  		if len(parts) == 3 { // If length is less than 3, then the path is simply "/inject".
  1260  			if strings.HasPrefix(parts[2], ":ENV:") {
  1261  				// Deprecated, not recommended.
  1262  				//    Note that this syntax fails validation when used to set injectionPath (i.e., service.path in mwh).
  1263  				//    It doesn't fail validation when used to set injectionURL, however. K8s bug maybe?
  1264  				pairs := strings.Split(parts[2], ":ENV:")
  1265  				for i := 1; i < len(pairs); i++ { // skip the first part, it is a nil
  1266  					pair := strings.SplitN(pairs[i], "=", 2)
  1267  					// The first part is the variable name which can not be empty
  1268  					// the second part is the variable value which can be empty but has to exist
  1269  					// for example, aaa=bbb, aaa= are valid, but =aaa or = are not valid, the
  1270  					// invalid ones will be ignored.
  1271  					if len(pair[0]) > 0 && len(pair) == 2 {
  1272  						newRes = append(newRes, pair...)
  1273  					}
  1274  				}
  1275  				return newRes
  1276  			}
  1277  			newRes = strings.Split(parts[2], "/")
  1278  		}
  1279  		for i, value := range newRes {
  1280  			if i%2 != 0 {
  1281  				// Replace --slash-- with / in values.
  1282  				newRes[i] = strings.ReplaceAll(value, "--slash--", "/")
  1283  			}
  1284  		}
  1285  		return newRes
  1286  	}(path)
  1287  	newEnvs := make(map[string]string)
  1288  
  1289  	for i := 0; i < len(res); i += 2 {
  1290  		k := res[i]
  1291  		if i == len(res)-1 { // ignore the last key without value
  1292  			log.Warnf("Odd number of inject env entries, ignore the last key %s\n", k)
  1293  			break
  1294  		}
  1295  
  1296  		env, found := URLParameterToEnv[k]
  1297  		if !found {
  1298  			env = strings.ToUpper(k) // if not found, use the custom env directly
  1299  		}
  1300  		if env != "" {
  1301  			newEnvs[env] = res[i+1]
  1302  		}
  1303  	}
  1304  
  1305  	return newEnvs
  1306  }
  1307  
  1308  func handleError(message string) {
  1309  	log.Errorf(message)
  1310  	totalFailedInjections.Increment()
  1311  }