From a63c85f0df6e0d1ed21da1a234eeb9b186d1a00e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Thu, 12 Dec 2024 22:44:39 +0000 Subject: [PATCH] fix(proxy-injector): handle proxy-log-level with quotes The proxy accepts log filters in the form `target[fields...]=level`, where a field may include a value match. This leads to log filters like `linkerd[name="outbound"]=debug`. When a log filter is configured via annotation or Helm, the proxy-injector fails to properly quote the log environment variable, leading to a failure to patch resources properly. To fix this, this change ensures that the log level is quoted, which properly escapes any quotes in the filter itself. --- charts/partials/templates/_proxy.tpl | 2 +- .../data/pod-inject-enabled-log-level.yaml | 17 + .../fake/data/pod-log-level.json | 401 ++++++++++++++++++ controller/proxy-injector/webhook_test.go | 29 ++ 4 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 controller/proxy-injector/fake/data/pod-inject-enabled-log-level.yaml create mode 100644 controller/proxy-injector/fake/data/pod-log-level.json diff --git a/charts/partials/templates/_proxy.tpl b/charts/partials/templates/_proxy.tpl index 4dcf12dee2c1c..346bc73c335ac 100644 --- a/charts/partials/templates/_proxy.tpl +++ b/charts/partials/templates/_proxy.tpl @@ -34,7 +34,7 @@ env: - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED value: {{.Values.proxy.enableShutdownEndpoint | quote}} - name: LINKERD2_PROXY_LOG - value: "{{.Values.proxy.logLevel}}{{ if not (eq .Values.proxy.logHTTPHeaders "insecure") }},[{headers}]=off,[{request}]=off{{ end }}" + value: {{ printf "%s%s" .Values.proxy.logLevel (.Values.proxy.logHTTPHeaders | eq "insecure" | ternary "" ",[{headers}]=off,[{request}]=off") | quote }} - name: LINKERD2_PROXY_LOG_FORMAT value: {{.Values.proxy.logFormat | quote}} - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR diff --git a/controller/proxy-injector/fake/data/pod-inject-enabled-log-level.yaml b/controller/proxy-injector/fake/data/pod-inject-enabled-log-level.yaml new file mode 100644 index 0000000000000..2ca8a7835fff7 --- /dev/null +++ b/controller/proxy-injector/fake/data/pod-inject-enabled-log-level.yaml @@ -0,0 +1,17 @@ +kind: Pod +apiVersion: apps/v1 +metadata: + name: nginx + namespace: kube-public + annotations: + config.linkerd.io/proxy-log-level: linkerd[name="inbound"]=trace,linkerd[name="outbound"]=debug,info + linkerd.io/inject: enabled + labels: + app: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - name: http + containerPort: 80 diff --git a/controller/proxy-injector/fake/data/pod-log-level.json b/controller/proxy-injector/fake/data/pod-log-level.json new file mode 100644 index 0000000000000..9de2dcdbebd75 --- /dev/null +++ b/controller/proxy-injector/fake/data/pod-log-level.json @@ -0,0 +1,401 @@ +[ + { + "op": "add", + "path": "/metadata/annotations/linkerd.io~1proxy-version", + "value": "dev-undefined" + }, + { + "op": "add", + "path": "/metadata/annotations/linkerd.io~1trust-root-sha256", + "value": "5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2" + }, + { + "op": "add", + "path": "/metadata/labels/linkerd.io~1control-plane-ns", + "value": "linkerd" + }, + { + "op": "add", + "path": "/metadata/labels/linkerd.io~1proxy-deployment", + "value": "owner-deployment" + }, + { + "op": "add", + "path": "/metadata/labels/linkerd.io~1workload-ns", + "value": "kube-public" + }, + { + "op": "add", + "path": "/spec/volumes", + "value": [] + }, + { + "op": "add", + "path": "/spec/initContainers", + "value": [] + }, + { + "op": "add", + "path": "/spec/volumes/-", + "value": { + "emptyDir": {}, + "name": "linkerd-proxy-init-xtables-lock" + } + }, + { + "op": "add", + "path": "/spec/initContainers/-", + "value": { + "args": [ + "--ipv6=false", + "--incoming-proxy-port", + "4143", + "--outgoing-proxy-port", + "4140", + "--proxy-uid", + "2102", + "--inbound-ports-to-ignore", + "4190,4191,4567,4568", + "--outbound-ports-to-ignore", + "4567,4568" + ], + "image": "cr.l5d.io/linkerd/proxy-init:v2.4.1", + "imagePullPolicy": "IfNotPresent", + "name": "linkerd-init", + "resources": null, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "add": [ + "NET_ADMIN", + "NET_RAW" + ] + }, + "privileged": false, + "runAsNonRoot": true, + "runAsUser": 65534, + "runAsGroup": 65534, + "readOnlyRootFilesystem": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "terminationMessagePolicy": "FallbackToLogsOnError", + "volumeMounts": [ + { + "mountPath": "/run", + "name": "linkerd-proxy-init-xtables-lock" + } + ] + } + }, + { + "op": "add", + "path": "/spec/volumes/-", + "value": { + "name": "linkerd-identity-end-entity", + "emptyDir": { + "medium": "Memory" + } + } + }, + { + "op": "add", + "path": "/spec/volumes/-", + "value": { + "name": "linkerd-identity-token", + "projected": { + "sources": [ + { + "serviceAccountToken": { + "audience": "identity.l5d.io", + "expirationSeconds": 86400, + "path": "linkerd-identity-token" + } + } + ] + } + } + }, + { + "op": "add", + "path": "/spec/containers/0", + "value": { + "env": [ + { + "name": "_pod_name", + "valueFrom": { + "fieldRef": { + "fieldPath": "metadata.name" + } + } + }, + { + "name": "_pod_ns", + "valueFrom": { + "fieldRef": { + "fieldPath": "metadata.namespace" + } + } + }, + { + "name": "_pod_nodeName", + "valueFrom": { + "fieldRef": { + "fieldPath": "spec.nodeName" + } + } + }, + { + "name": "LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED", + "value": "false" + }, + { + "name": "LINKERD2_PROXY_LOG", + "value": "linkerd[name=\"inbound\"]=trace,linkerd[name=\"outbound\"]=debug,info,[{headers}]=off,[{request}]=off" + }, + { + "name": "LINKERD2_PROXY_LOG_FORMAT", + "value": "plain" + }, + { + "name": "LINKERD2_PROXY_DESTINATION_SVC_ADDR", + "value": "linkerd-dst-headless.linkerd.svc.cluster.local.:8086" + }, + { + "name": "LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS", + "value": "10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8" + }, + { + "name": "LINKERD2_PROXY_POLICY_SVC_ADDR", + "value": "linkerd-policy.linkerd.svc.cluster.local.:8090" + }, + { + "name": "LINKERD2_PROXY_POLICY_WORKLOAD", + "value": "{\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n" + }, + { + "name": "LINKERD2_PROXY_INBOUND_DEFAULT_POLICY", + "value": "all-unauthenticated" + }, + { + "name": "LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS", + "value": "10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8" + }, + { + "name": "LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT", + "value": "3s" + }, + { + "name": "LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT", + "value": "5m" + }, + { + "name": "LINKERD2_PROXY_CONTROL_STREAM_LIFETIME", + "value": "1h" + }, + { + "name": "LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT", + "value": "100ms" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT", + "value": "1000ms" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT", + "value": "5s" + }, + { + "name": "LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT", + "value": "90s" + }, + { + "name": "LINKERD2_PROXY_CONTROL_LISTEN_ADDR", + "value": "0.0.0.0:4190" + }, + { + "name": "LINKERD2_PROXY_ADMIN_LISTEN_ADDR", + "value": "0.0.0.0:4191" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR", + "value": "127.0.0.1:4140" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS", + "value": "127.0.0.1:4140" + }, + { + "name": "LINKERD2_PROXY_INBOUND_LISTEN_ADDR", + "value": "0.0.0.0:4143" + }, + { + "name": "LINKERD2_PROXY_INBOUND_IPS", + "valueFrom": { + "fieldRef": { + "fieldPath": "status.podIPs" + } + } + }, + { + "name": "LINKERD2_PROXY_INBOUND_PORTS", + "value": "80" + }, + { + "name": "LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES", + "value": "svc.cluster.local." + }, + { + "name": "LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE", + "value": "10000ms" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE", + "value": "10000ms" + }, + { + "name": "LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT", + "value": "30s" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT", + "value": "30s" + }, + { + "name": "LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL", + "value": "10s" + }, + { + "name": "LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT", + "value": "3s" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL", + "value": "10s" + }, + { + "name": "LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT", + "value": "3s" + }, + { + "name": "LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION", + "value": "25,587,3306,4444,5432,6379,9300,11211" + }, + { + "name": "LINKERD2_PROXY_DESTINATION_CONTEXT", + "value": "{\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n" + }, + { + "name": "_pod_sa", + "valueFrom": { + "fieldRef": { + "fieldPath": "spec.serviceAccountName" + } + } + }, + { + "name": "_l5d_ns", + "value": "linkerd" + }, + { + "name": "_l5d_trustdomain", + "value": "cluster.local" + }, + { + "name": "LINKERD2_PROXY_IDENTITY_DIR", + "value": "/var/run/linkerd/identity/end-entity" + }, + { + "name": "LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS", + "value": "IdentityTrustAnchorsPEM\n" + }, + { + "name": "LINKERD2_PROXY_IDENTITY_TOKEN_FILE", + "value": "/var/run/secrets/tokens/linkerd-identity-token" + }, + { + "name": "LINKERD2_PROXY_IDENTITY_SVC_ADDR", + "value": "linkerd-identity-headless.linkerd.svc.cluster.local.:8080" + }, + { + "name": "LINKERD2_PROXY_IDENTITY_LOCAL_NAME", + "value": "$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local" + }, + { + "name": "LINKERD2_PROXY_IDENTITY_SVC_NAME", + "value": "linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local" + }, + { + "name": "LINKERD2_PROXY_DESTINATION_SVC_NAME", + "value": "linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local" + }, + { + "name": "LINKERD2_PROXY_POLICY_SVC_NAME", + "value": "linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local" + } + ], + "image": "cr.l5d.io/linkerd/proxy:dev-undefined", + "imagePullPolicy": "IfNotPresent", + "lifecycle": { + "postStart": { + "exec": { + "command": [ + "/usr/lib/linkerd/linkerd-await", + "--timeout=2m", + "--port=4191" + ] + } + } + }, + "livenessProbe": { + "httpGet": { + "path": "/live", + "port": 4191 + }, + "initialDelaySeconds": 10, + "timeoutSeconds": 1 + }, + "name": "linkerd-proxy", + "ports": [ + { + "containerPort": 4143, + "name": "linkerd-proxy" + }, + { + "containerPort": 4191, + "name": "linkerd-admin" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/ready", + "port": 4191 + }, + "initialDelaySeconds": 2, + "timeoutSeconds": 1 + }, + "resources": null, + "securityContext": { + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true, + "runAsNonRoot": true, + "runAsUser": 2102, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "terminationMessagePolicy": "FallbackToLogsOnError", + "volumeMounts": [ + { + "mountPath": "/var/run/linkerd/identity/end-entity", + "name": "linkerd-identity-end-entity" + }, + { + "mountPath": "/var/run/secrets/tokens", + "name": "linkerd-identity-token" + } + ] + } + } +] diff --git a/controller/proxy-injector/webhook_test.go b/controller/proxy-injector/webhook_test.go index 3d2d28684c679..fb94cc362e5a1 100644 --- a/controller/proxy-injector/webhook_test.go +++ b/controller/proxy-injector/webhook_test.go @@ -98,6 +98,11 @@ func TestGetPodPatch(t *testing.T) { ns: nsDisabled, conf: confNsDisabled(), }, + { + filename: "pod-inject-enabled.yaml", + ns: nsDisabled, + conf: confNsDisabled(), + }, { filename: "pod-with-debug-disabled.yaml", ns: nsDisabled, @@ -152,6 +157,30 @@ func TestGetPodPatch(t *testing.T) { } }) + t.Run("by configuring log level", func(t *testing.T) { + _, expectedPatch := loadPatch(factory, t, "pod-log-level.json") + + pod := fileContents(factory, t, "pod-inject-enabled-log-level.yaml") + fakeReq := getFakePodReq(pod) + conf := confNsWithoutOpaquePorts(). + WithKind(fakeReq.Kind.Kind). + WithOwnerRetriever(ownerRetrieverFake) + _, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw) + if err != nil { + t.Fatal(err) + } + + patchJSON, err := conf.GetPodPatch(true) + if err != nil { + t.Fatalf("Unexpected PatchForAdmissionRequest error: %s", err) + } + + actualPatch := unmarshalPatch(t, patchJSON) + if diff := deep.Equal(expectedPatch, actualPatch); diff != nil { + t.Fatalf("The actual patch didn't match what was expected.\n%+v", diff) + } + }) + t.Run("by checking pod inherits config annotations from namespace", func(t *testing.T) { _, expectedPatch := loadPatch(factory, t, "pod-with-ns-annotations.patch.json")