...

Source file src/github.com/linkerd/linkerd2/pkg/inject/report.go

Documentation: github.com/linkerd/linkerd2/pkg/inject

     1  package inject
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/linkerd/linkerd2/pkg/healthcheck"
     9  	"github.com/linkerd/linkerd2/pkg/k8s"
    10  	v1 "k8s.io/api/core/v1"
    11  )
    12  
    13  const (
    14  	hostNetworkEnabled                   = "host_network_enabled"
    15  	sidecarExists                        = "sidecar_already_exists"
    16  	unsupportedResource                  = "unsupported_resource"
    17  	injectEnableAnnotationAbsent         = "injection_enable_annotation_absent"
    18  	injectDisableAnnotationPresent       = "injection_disable_annotation_present"
    19  	annotationAtNamespace                = "namespace"
    20  	annotationAtWorkload                 = "workload"
    21  	invalidInjectAnnotationWorkload      = "invalid_inject_annotation_at_workload"
    22  	invalidInjectAnnotationNamespace     = "invalid_inject_annotation_at_ns"
    23  	disabledAutomountServiceAccountToken = "disabled_automount_service_account_token_account"
    24  	udpPortsEnabled                      = "udp_ports_enabled"
    25  )
    26  
    27  var (
    28  	// Reasons is a map of inject skip reasons with human readable sentences
    29  	Reasons = map[string]string{
    30  		hostNetworkEnabled:                   "hostNetwork is enabled",
    31  		sidecarExists:                        "pod has a sidecar injected already",
    32  		unsupportedResource:                  "this resource kind is unsupported",
    33  		injectEnableAnnotationAbsent:         fmt.Sprintf("neither the namespace nor the pod have the annotation \"%s:%s\"", k8s.ProxyInjectAnnotation, k8s.ProxyInjectEnabled),
    34  		injectDisableAnnotationPresent:       fmt.Sprintf("pod has the annotation \"%s:%s\"", k8s.ProxyInjectAnnotation, k8s.ProxyInjectDisabled),
    35  		invalidInjectAnnotationWorkload:      fmt.Sprintf("invalid value for annotation \"%s\" at workload", k8s.ProxyInjectAnnotation),
    36  		invalidInjectAnnotationNamespace:     fmt.Sprintf("invalid value for annotation \"%s\" at namespace", k8s.ProxyInjectAnnotation),
    37  		disabledAutomountServiceAccountToken: "automountServiceAccountToken set to \"false\", with Values.identity.serviceAccountTokenProjection set to \"false\"",
    38  		udpPortsEnabled:                      "UDP port(s) configured on pod spec",
    39  	}
    40  )
    41  
    42  // Report contains the Kind and Name for a given workload along with booleans
    43  // describing the result of the injection transformation
    44  type Report struct {
    45  	Kind                         string
    46  	Name                         string
    47  	HostNetwork                  bool
    48  	Sidecar                      bool
    49  	UDP                          bool // true if any port in any container has `protocol: UDP`
    50  	UnsupportedResource          bool // unsupported to inject
    51  	InjectDisabled               bool
    52  	InjectDisabledReason         string
    53  	InjectAnnotationAt           string
    54  	Annotatable                  bool
    55  	Annotated                    bool
    56  	AutomountServiceAccountToken bool
    57  
    58  	// Uninjected consists of two boolean flags to indicate if a proxy and
    59  	// proxy-init containers have been uninjected in this report
    60  	Uninjected struct {
    61  		// Proxy is true if a proxy container has been uninjected
    62  		Proxy bool
    63  
    64  		// ProxyInit is true if a proxy-init container has been uninjected
    65  		ProxyInit bool
    66  	}
    67  }
    68  
    69  // newReport returns a new Report struct, initialized with the Kind and Name
    70  // from conf
    71  func newReport(conf *ResourceConfig) *Report {
    72  	var name string
    73  	if conf.IsPod() {
    74  		name = conf.pod.meta.Name
    75  		if name == "" {
    76  			name = conf.pod.meta.GenerateName
    77  		}
    78  	} else if m := conf.workload.Meta; m != nil {
    79  		name = m.Name
    80  	}
    81  
    82  	report := &Report{
    83  		Kind:                         strings.ToLower(conf.workload.metaType.Kind),
    84  		Name:                         name,
    85  		AutomountServiceAccountToken: true,
    86  	}
    87  
    88  	if conf.HasPodTemplate() {
    89  		report.InjectDisabled, report.InjectDisabledReason, report.InjectAnnotationAt = report.disabledByAnnotation(conf)
    90  		report.HostNetwork = conf.pod.spec.HostNetwork
    91  		report.Sidecar = healthcheck.HasExistingSidecars(conf.pod.spec)
    92  		report.UDP = checkUDPPorts(conf.pod.spec)
    93  		if conf.pod.spec.AutomountServiceAccountToken != nil &&
    94  			(conf.values != nil && !conf.values.Identity.ServiceAccountTokenProjection) {
    95  			report.AutomountServiceAccountToken = *conf.pod.spec.AutomountServiceAccountToken
    96  		}
    97  		if conf.origin == OriginWebhook {
    98  			if vm := conf.serviceAccountVolumeMount(); vm == nil {
    99  				// set to false only if it is not using the new linkerd-token volume projection
   100  				if conf.values != nil && !conf.values.Identity.ServiceAccountTokenProjection {
   101  					report.AutomountServiceAccountToken = false
   102  				}
   103  			}
   104  		}
   105  	} else {
   106  		report.UnsupportedResource = true
   107  	}
   108  
   109  	if conf.HasPodTemplate() || conf.IsService() || conf.IsNamespace() {
   110  		report.Annotatable = true
   111  	}
   112  
   113  	return report
   114  }
   115  
   116  // ResName returns a string "Kind/Name" for the workload referred in the report r
   117  func (r *Report) ResName() string {
   118  	return fmt.Sprintf("%s/%s", r.Kind, r.Name)
   119  }
   120  
   121  // Injectable returns false if the report flags indicate that the workload is on a host network
   122  // or there is already a sidecar or the resource is not supported or inject is explicitly disabled.
   123  // If false, the second returned value describes the reason.
   124  func (r *Report) Injectable() (bool, []string) {
   125  	var reasons []string
   126  	if r.HostNetwork {
   127  		reasons = append(reasons, hostNetworkEnabled)
   128  	}
   129  	if r.Sidecar {
   130  		reasons = append(reasons, sidecarExists)
   131  	}
   132  	if r.UnsupportedResource {
   133  		reasons = append(reasons, unsupportedResource)
   134  	}
   135  	if r.InjectDisabled {
   136  		reasons = append(reasons, r.InjectDisabledReason)
   137  	}
   138  
   139  	if !r.AutomountServiceAccountToken {
   140  		reasons = append(reasons, disabledAutomountServiceAccountToken)
   141  	}
   142  
   143  	if len(reasons) > 0 {
   144  		return false, reasons
   145  	}
   146  	return true, nil
   147  }
   148  
   149  // IsAnnotatable returns true if the resource for a report can be annotated.
   150  func (r *Report) IsAnnotatable() bool {
   151  	return r.Annotatable
   152  }
   153  
   154  func checkUDPPorts(t *v1.PodSpec) bool {
   155  	// Check for ports with `protocol: UDP`, which will not be routed by Linkerd
   156  	for _, container := range t.Containers {
   157  		for _, port := range container.Ports {
   158  			if port.Protocol == v1.ProtocolUDP {
   159  				return true
   160  			}
   161  		}
   162  	}
   163  	return false
   164  }
   165  
   166  // disabledByAnnotation checks the workload and namespace for the annotation
   167  // that disables injection. It returns if it is disabled, why it is disabled,
   168  // and the location where the annotation was present.
   169  func (r *Report) disabledByAnnotation(conf *ResourceConfig) (bool, string, string) {
   170  	// truth table of the effects of the inject annotation:
   171  	//
   172  	// origin  | namespace | pod      | inject?  | return
   173  	// ------- | --------- | -------- | -------- | ------
   174  	// webhook | enabled   | enabled  | yes      | false
   175  	// webhook | enabled   | ""       | yes      | false
   176  	// webhook | enabled   | disabled | no       | true
   177  	// webhook | disabled  | enabled  | yes      | false
   178  	// webhook | ""        | enabled  | yes      | false
   179  	// webhook | disabled  | disabled | no       | true
   180  	// webhook | ""        | disabled | no       | true
   181  	// webhook | disabled  | ""       | no       | true
   182  	// webhook | ""        | ""       | no       | true
   183  	// cli     | n/a       | enabled  | yes      | false
   184  	// cli     | n/a       | ""       | yes      | false
   185  	// cli     | n/a       | disabled | no       | true
   186  
   187  	podAnnotation := conf.pod.meta.Annotations[k8s.ProxyInjectAnnotation]
   188  	nsAnnotation := conf.nsAnnotations[k8s.ProxyInjectAnnotation]
   189  
   190  	if conf.origin == OriginCLI {
   191  		return podAnnotation == k8s.ProxyInjectDisabled, "", ""
   192  	}
   193  
   194  	if !isInjectAnnotationValid(nsAnnotation) {
   195  		return true, invalidInjectAnnotationNamespace, ""
   196  	}
   197  
   198  	if !isInjectAnnotationValid(podAnnotation) {
   199  		return true, invalidInjectAnnotationWorkload, ""
   200  	}
   201  
   202  	if nsAnnotation == k8s.ProxyInjectEnabled || nsAnnotation == k8s.ProxyInjectIngress {
   203  		if podAnnotation == k8s.ProxyInjectDisabled {
   204  			return true, injectDisableAnnotationPresent, annotationAtWorkload
   205  		}
   206  		return false, "", annotationAtNamespace
   207  	}
   208  
   209  	if podAnnotation != k8s.ProxyInjectEnabled && podAnnotation != k8s.ProxyInjectIngress {
   210  		return true, injectEnableAnnotationAbsent, ""
   211  	}
   212  
   213  	return false, "", annotationAtWorkload
   214  }
   215  
   216  func isInjectAnnotationValid(annotation string) bool {
   217  	if annotation != "" && !(annotation == k8s.ProxyInjectEnabled || annotation == k8s.ProxyInjectDisabled || annotation == k8s.ProxyInjectIngress) {
   218  		return false
   219  	}
   220  	return true
   221  }
   222  
   223  // ThrowInjectError errors out `inject` when the report contains errors
   224  // related to automountServiceAccountToken, hostNetwork, existing sidecar,
   225  // or udp ports
   226  // See - https://github.com/linkerd/linkerd2/issues/4214
   227  func (r *Report) ThrowInjectError() []error {
   228  
   229  	errs := []error{}
   230  
   231  	if !r.AutomountServiceAccountToken {
   232  		errs = append(errs, errors.New(Reasons[disabledAutomountServiceAccountToken]))
   233  	}
   234  
   235  	if r.HostNetwork {
   236  		errs = append(errs, errors.New(Reasons[hostNetworkEnabled]))
   237  	}
   238  
   239  	if r.Sidecar {
   240  		errs = append(errs, errors.New(Reasons[sidecarExists]))
   241  	}
   242  
   243  	if r.UDP {
   244  		errs = append(errs, errors.New(Reasons[udpPortsEnabled]))
   245  	}
   246  
   247  	return errs
   248  }
   249  

View as plain text