...

Source file src/github.com/linkerd/linkerd2/controller/proxy-injector/webhook.go

Documentation: github.com/linkerd/linkerd2/controller/proxy-injector

     1  package injector
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/linkerd/linkerd2/controller/k8s"
    10  	"github.com/linkerd/linkerd2/controller/webhook"
    11  	"github.com/linkerd/linkerd2/pkg/config"
    12  	"github.com/linkerd/linkerd2/pkg/inject"
    13  	pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
    14  	"github.com/linkerd/linkerd2/pkg/version"
    15  	"github.com/sirupsen/logrus"
    16  	admissionv1beta1 "k8s.io/api/admission/v1beta1"
    17  	v1 "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/labels"
    20  	"k8s.io/client-go/tools/record"
    21  )
    22  
    23  const (
    24  	eventTypeSkipped  = "InjectionSkipped"
    25  	eventTypeInjected = "Injected"
    26  )
    27  
    28  var log = logrus.New()
    29  
    30  // Inject returns the function that produces an AdmissionResponse containing
    31  // the patch, if any, to apply to the pod (proxy sidecar and eventually the
    32  // init container to set it up)
    33  func Inject(linkerdNamespace string) webhook.Handler {
    34  	return func(
    35  		ctx context.Context,
    36  		api *k8s.MetadataAPI,
    37  		request *admissionv1beta1.AdmissionRequest,
    38  		recorder record.EventRecorder,
    39  	) (*admissionv1beta1.AdmissionResponse, error) {
    40  		log.Debugf("request object bytes: %s", request.Object.Raw)
    41  
    42  		// Build the resource config based off the request metadata and kind of
    43  		// object. This is later used to build the injection report and generated
    44  		// patch.
    45  		valuesConfig, err := config.Values(pkgK8s.MountPathValuesConfig)
    46  		if err != nil {
    47  			return nil, err
    48  		}
    49  
    50  		caPEM, err := os.ReadFile(pkgK8s.MountPathTrustRootsPEM)
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  		valuesConfig.IdentityTrustAnchorsPEM = string(caPEM)
    55  
    56  		ns, err := api.Get(k8s.NS, request.Namespace)
    57  		if err != nil {
    58  			return nil, err
    59  		}
    60  		resourceConfig := inject.NewResourceConfig(valuesConfig, inject.OriginWebhook, linkerdNamespace).
    61  			WithOwnerRetriever(ownerRetriever(ctx, api, request.Namespace)).
    62  			WithNsAnnotations(ns.GetAnnotations()).
    63  			WithKind(request.Kind.Kind)
    64  
    65  		// Build the injection report.
    66  		report, err := resourceConfig.ParseMetaAndYAML(request.Object.Raw)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  		log.Infof("received %s", report.ResName())
    71  
    72  		// If the resource has an owner, then it should be retrieved for recording
    73  		// events.
    74  		var parent *metav1.PartialObjectMetadata
    75  		var ownerKind string
    76  		if ownerRef := resourceConfig.GetOwnerRef(); ownerRef != nil {
    77  			res, err := k8s.GetAPIResource(ownerRef.Kind)
    78  			if err != nil {
    79  				log.Tracef("skipping event for parent %s: %s", ownerRef.Kind, err)
    80  			} else {
    81  				objs, err := api.GetByNamespaceFiltered(res, request.Namespace, ownerRef.Name, labels.Everything())
    82  				if err != nil {
    83  					log.Warnf("couldn't retrieve parent object %s-%s-%s; error: %s", request.Namespace, ownerRef.Kind, ownerRef.Name, err)
    84  				} else if len(objs) == 0 {
    85  					log.Warnf("couldn't retrieve parent object %s-%s-%s", request.Namespace, ownerRef.Kind, ownerRef.Name)
    86  				} else {
    87  					parent = objs[0]
    88  				}
    89  				ownerKind = strings.ToLower(ownerRef.Kind)
    90  			}
    91  		}
    92  
    93  		configLabels := configToPrometheusLabels(resourceConfig)
    94  		proxyInjectionAdmissionRequests.With(admissionRequestLabels(ownerKind, request.Namespace, report.InjectAnnotationAt, configLabels)).Inc()
    95  
    96  		// If the resource is injectable then admit it after creating a patch that
    97  		// adds the proxy-init and proxy containers.
    98  		injectable, reasons := report.Injectable()
    99  		if injectable {
   100  			resourceConfig.AppendPodAnnotation(pkgK8s.CreatedByAnnotation, fmt.Sprintf("linkerd/proxy-injector %s", version.Version))
   101  
   102  			// If namespace has annotations that do not exist on pod then copy them
   103  			// over to pod's template.
   104  			inject.AppendNamespaceAnnotations(resourceConfig.GetOverrideAnnotations(), resourceConfig.GetNsAnnotations(), resourceConfig.GetWorkloadAnnotations())
   105  
   106  			// If the pod did not inherit the opaque ports annotation from the
   107  			// namespace, then add the default value from the config values. This
   108  			// ensures that the generated patch always sets the opaque ports
   109  			// annotation.
   110  			if !resourceConfig.HasWorkloadAnnotation(pkgK8s.ProxyOpaquePortsAnnotation) {
   111  				defaultPorts := strings.Split(resourceConfig.GetValues().Proxy.OpaquePorts, ",")
   112  				filteredPorts := resourceConfig.FilterPodOpaquePorts(defaultPorts)
   113  				// Only add the annotation if there are ports that the pod exposes
   114  				// that are in the default opaque ports list.
   115  				if len(filteredPorts) != 0 {
   116  					ports := strings.Join(filteredPorts, ",")
   117  					resourceConfig.AppendPodAnnotation(pkgK8s.ProxyOpaquePortsAnnotation, ports)
   118  				}
   119  			}
   120  
   121  			patchJSON, err := resourceConfig.GetPodPatch(true)
   122  			if err != nil {
   123  				return nil, err
   124  			}
   125  			if parent != nil {
   126  				recorder.Event(parent, v1.EventTypeNormal, eventTypeInjected, "Linkerd sidecar proxy injected")
   127  			}
   128  			log.Infof("injection patch generated for: %s", report.ResName())
   129  			log.Debugf("injection patch: %s", patchJSON)
   130  			proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "false", "", report.InjectAnnotationAt, configLabels)).Inc()
   131  			patchType := admissionv1beta1.PatchTypeJSONPatch
   132  			return &admissionv1beta1.AdmissionResponse{
   133  				UID:       request.UID,
   134  				Allowed:   true,
   135  				PatchType: &patchType,
   136  				Patch:     patchJSON,
   137  			}, nil
   138  		}
   139  
   140  		// Resource could not be injected with the sidecar, format the reason
   141  		// for injection being skipped to emit an event
   142  		readableReasons := make([]string, 0, len(reasons))
   143  		for _, reason := range reasons {
   144  			readableReasons = append(readableReasons, inject.Reasons[reason])
   145  		}
   146  		readableMsg := strings.Join(readableReasons, ", ")
   147  
   148  		if parent != nil {
   149  			recorder.Eventf(parent, v1.EventTypeNormal, eventTypeSkipped, "Linkerd sidecar proxy injection skipped: %s", readableMsg)
   150  		}
   151  
   152  		// Create a patch which adds the opaque ports annotation if the workload
   153  		// doesn't already have it set.
   154  		patchJSON, err := resourceConfig.CreateOpaquePortsPatch()
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  
   159  		// If resource needs to be patched with annotations (e.g opaque
   160  		// ports), then admit the request with the relevant patch
   161  		if len(patchJSON) != 0 {
   162  			log.Infof("annotation patch generated for: %s", report.ResName())
   163  			log.Debugf("annotation patch: %s", patchJSON)
   164  			proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "false", "", report.InjectAnnotationAt, configLabels)).Inc()
   165  			patchType := admissionv1beta1.PatchTypeJSONPatch
   166  			return &admissionv1beta1.AdmissionResponse{
   167  				UID:       request.UID,
   168  				Allowed:   true,
   169  				PatchType: &patchType,
   170  				Patch:     patchJSON,
   171  			}, nil
   172  		}
   173  
   174  		// If the resource is a pod, and no annotation patch has
   175  		// been generated, record in the metrics (and log) that it has been
   176  		// entirely skipped and admit without any mutations
   177  		if resourceConfig.IsPod() {
   178  			log.Infof("skipped %s: %s", report.ResName(), readableMsg)
   179  			proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "true", strings.Join(reasons, ","), report.InjectAnnotationAt, configLabels)).Inc()
   180  			return &admissionv1beta1.AdmissionResponse{
   181  				UID:     request.UID,
   182  				Allowed: true,
   183  			}, nil
   184  		}
   185  
   186  		return &admissionv1beta1.AdmissionResponse{
   187  			UID:     request.UID,
   188  			Allowed: true,
   189  		}, nil
   190  	}
   191  }
   192  
   193  func ownerRetriever(ctx context.Context, api *k8s.MetadataAPI, ns string) inject.OwnerRetrieverFunc {
   194  	return func(p *v1.Pod) (string, string, error) {
   195  		p.SetNamespace(ns)
   196  		return api.GetOwnerKindAndName(ctx, p, true)
   197  	}
   198  }
   199  

View as plain text