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 := ¶ms.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 := ¶ms.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 }