Repository: linkerd/linkerd2
Pull request #10800 (2023-04-21 21:35:36)

stable-2.12.5
## stable-2.12.5

This stable release fixes an incompatibility issue with the AWS CNI addon in EKS
that was forbidding pods to acquire networking after scaling up nodes (thanks
@frimik!). It also includes security updates for dependencies.

* Detached the linkerd-cni plugin's version from linkerd's and bumped to v1.1.1
  to fix incompatibility with EKS' AWS CNI addon
* Bumped the memory limit for the no-op init container to 25Mi to address issues
  on OKE environments
* Updated `h2` dependency in the policy controller to include a patch for a
  theoretical denial-of-service vulnerability discovered in CVE-2023-26964
* Updated `openssl` dependency in the policy controller, addressing
  RUSTSEC-2023-0022, RUSTSEC-2023-0023 and RUSTSEC-2023-0024


alpeb: Currently blocked on #10802

Patch:
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 08a5c3288..19838ccf8 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -257,7 +257,7 @@ jobs:
           - multicluster
           - uninstall
           # - upgrade-edge
-          - upgrade-stable
+          # - upgrade-stable
     continue-on-error: true
     runs-on: ubuntu-20.04
     timeout-minutes: 15
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index bb7dac6fc..4d90456e5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -172,7 +172,7 @@ jobs:
         #- helm-upgrade
         - uninstall
         # - upgrade-edge
-        - upgrade-stable
+        # - upgrade-stable
     timeout-minutes: 60
     runs-on: ubuntu-20.04
     steps:
diff --git a/.golangci.yml b/.golangci.yml
index 85badc2fa..a11d28b35 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -48,6 +48,14 @@ linters:
   disable:
   - structcheck
 
+linters-settings:
+  revive:
+    rules:
+    - name: package-comments
+      disabled: true
+  stylecheck:
+    checks: ["ST1019"]
+
 issues:
   exclude-use-default: false
   exclude-rules:
diff --git a/CHANGES.md b/CHANGES.md
index 91ad91d3c..cba4ea43c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,20 @@
 # Changes
 
+## stable-2.12.5
+
+This stable release fixes an incompatibility issue with the AWS CNI addon in EKS
+that was forbidding pods to acquire networking after scaling up nodes (thanks
+@frimik!). It also includes security updates for dependencies.
+
+* Detached the linkerd-cni plugin's version from linkerd's and bumped to v1.1.1
+  to fix incompatibility with EKS' AWS CNI addon
+* Bumped the memory limit for the no-op init container to 25Mi to address issues
+  on OKE environments
+* Updated `h2` dependency in the policy controller to include a patch for a
+  theoretical denial-of-service vulnerability discovered in CVE-2023-26964
+* Updated `openssl` dependency in the policy controller, addressing
+  RUSTSEC-2023-0022, RUSTSEC-2023-0023 and RUSTSEC-2023-0024
+
 ## stable-2.12.4
 
 This stable release fixes a memory leak in the Destination controller, and also
diff --git a/Cargo.lock b/Cargo.lock
index 366696786..6a72df0a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -529,9 +529,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.3.12"
+version = "0.3.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b"
+checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21"
 dependencies = [
  "bytes",
  "fnv",
@@ -542,7 +542,7 @@ dependencies = [
  "indexmap",
  "slab",
  "tokio",
- "tokio-util 0.6.10",
+ "tokio-util",
  "tracing",
 ]
 
@@ -905,7 +905,7 @@ dependencies = [
  "tokio",
  "tokio-native-tls",
  "tokio-tungstenite",
- "tokio-util 0.7.0",
+ "tokio-util",
  "tower",
  "tower-http",
  "tracing",
@@ -962,7 +962,7 @@ dependencies = [
  "smallvec",
  "thiserror",
  "tokio",
- "tokio-util 0.7.0",
+ "tokio-util",
  "tracing",
 ]
 
@@ -1285,9 +1285,9 @@ checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
 
 [[package]]
 name = "openssl"
-version = "0.10.45"
+version = "0.10.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
+checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
 dependencies = [
  "bitflags",
  "cfg-if",
@@ -1317,9 +1317,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.80"
+version = "0.9.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
+checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
 dependencies = [
  "autocfg",
  "cc",
@@ -2026,7 +2026,7 @@ dependencies = [
  "futures-core",
  "pin-project-lite",
  "tokio",
- "tokio-util 0.7.0",
+ "tokio-util",
 ]
 
 [[package]]
@@ -2056,31 +2056,17 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.6.10"
+version = "0.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507"
+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
 dependencies = [
  "bytes",
  "futures-core",
  "futures-sink",
- "log",
- "pin-project-lite",
- "tokio",
-]
-
-[[package]]
-name = "tokio-util"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1"
-dependencies = [
- "bytes",
- "futures-core",
- "futures-sink",
- "log",
  "pin-project-lite",
  "slab",
  "tokio",
+ "tracing",
 ]
 
 [[package]]
@@ -2102,7 +2088,7 @@ dependencies = [
  "prost",
  "prost-derive",
  "tokio-stream",
- "tokio-util 0.7.0",
+ "tokio-util",
  "tower-layer",
  "tower-service",
  "tracing",
@@ -2119,7 +2105,7 @@ dependencies = [
  "pin-project",
  "pin-project-lite",
  "tokio",
- "tokio-util 0.7.0",
+ "tokio-util",
  "tower-layer",
  "tower-service",
  "tracing",
diff --git a/RELEASE.md b/RELEASE.md
index e248df52a..b1b73c656 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -41,7 +41,7 @@ following updates are needed:
    var ProxyInitVersion = "v1.2.0"
    ```
 
-- `charts/linkerd2/values.yaml`
+- `charts/linkerd-control-plane/values.yaml`
 
    Upgrade the version in `global.proxyInit.image.version`
 
diff --git a/bin/_docker.sh b/bin/_docker.sh
index defc9605a..02dd0ac2f 100644
--- a/bin/_docker.sh
+++ b/bin/_docker.sh
@@ -30,7 +30,6 @@ export SUPPORTED_ARCHS=${SUPPORTED_ARCHS:-linux/amd64,linux/arm64,linux/arm/v7}
 # shellcheck disable=SC2206
 export DOCKER_IMAGES=(${DOCKER_IMAGES:-
     cli-bin
-    cni-plugin
     controller
     policy-controller
     metrics-api
diff --git a/bin/docker-build b/bin/docker-build
index 853c3b3ed..4fb450ce9 100755
--- a/bin/docker-build
+++ b/bin/docker-build
@@ -13,7 +13,6 @@ bindir=$( cd "${0%/*}" && pwd )
 "$bindir"/docker-build-controller
 "$bindir"/docker-build-policy-controller
 "$bindir"/docker-build-web
-"$bindir"/docker-build-cni-plugin
 "$bindir"/docker-build-debug
 if [ -z "${LINKERD_LOCAL_BUILD_CLI:-}" ]; then
     "$bindir"/docker-build-cli-bin
diff --git a/charts/linkerd-control-plane/Chart.yaml b/charts/linkerd-control-plane/Chart.yaml
index ad35605b3..e8a960661 100644
--- a/charts/linkerd-control-plane/Chart.yaml
+++ b/charts/linkerd-control-plane/Chart.yaml
@@ -16,7 +16,7 @@ dependencies:
 - name: partials
   version: 0.1.0
   repository: file://../partials
-version: 1.9.6
+version: 1.9.7
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/charts/linkerd-control-plane/README.md b/charts/linkerd-control-plane/README.md
index 9df77fca2..ba586dba2 100644
--- a/charts/linkerd-control-plane/README.md
+++ b/charts/linkerd-control-plane/README.md
@@ -3,7 +3,7 @@
 Linkerd gives you observability, reliability, and security
 for your microservices — with no code change required.
 
-![Version: 1.9.6](https://img.shields.io/badge/Version-1.9.6-informational?style=flat-square)
+![Version: 1.9.7](https://img.shields.io/badge/Version-1.9.7-informational?style=flat-square)
 ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/charts/linkerd2-cni/Chart.yaml b/charts/linkerd2-cni/Chart.yaml
index f8fc48b7a..bc8da68cd 100644
--- a/charts/linkerd2-cni/Chart.yaml
+++ b/charts/linkerd2-cni/Chart.yaml
@@ -9,4 +9,4 @@ description: |
 kubeVersion: ">=1.21.0-0"
 icon: https://linkerd.io/images/logo-only-200h.png
 name: "linkerd2-cni"
-version: 30.3.6
+version: 30.3.7
diff --git a/charts/linkerd2-cni/README.md b/charts/linkerd2-cni/README.md
index 601cdf373..604915da1 100644
--- a/charts/linkerd2-cni/README.md
+++ b/charts/linkerd2-cni/README.md
@@ -6,7 +6,7 @@ Linkerd [CNI plugin](https://linkerd.io/2/features/cni/) takes care of setting
 up your pod's network so  incoming and outgoing traffic is proxied through the
 data plane.
 
-![Version: 30.3.6](https://img.shields.io/badge/Version-30.3.6-informational?style=flat-square)
+![Version: 30.3.7](https://img.shields.io/badge/Version-30.3.7-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
@@ -22,14 +22,15 @@ Kubernetes: `>=1.21.0-0`
 
 | Key | Type | Default | Description |
 |-----|------|---------|-------------|
-| cniPluginImage | string | `"cr.l5d.io/linkerd/cni-plugin"` | Docker image for the CNI plugin |
-| cniPluginVersion | string | `"linkerdVersionValue"` | Tag for the CNI container Docker image |
 | destCNIBinDir | string | `"/opt/cni/bin"` | Directory on the host where the CNI configuration will be placed |
 | destCNINetDir | string | `"/etc/cni/net.d"` | Directory on the host where the CNI plugin binaries reside |
 | enablePSP | bool | `false` | Add a PSP resource and bind it to the linkerd-cni ServiceAccounts. Note PSP has been deprecated since k8s v1.21 |
 | extraInitContainers | list | `[]` | Add additional initContainers to the daemonset |
 | ignoreInboundPorts | string | `""` | Default set of inbound ports to skip via iptables |
 | ignoreOutboundPorts | string | `""` | Default set of outbound ports to skip via iptables |
+| image.name | string | `"cr.l5d.io/linkerd/cni-plugin"` | Docker image for the CNI plugin |
+| image.pullPolicy | string | `"IfNotPresent"` | Pull policy for the linkerd-cni container |
+| image.version | string | `"v1.1.1"` | Tag for the CNI container Docker image |
 | imagePullSecrets | string | `nil` |  |
 | inboundProxyPort | int | `4143` | Inbound port for the proxy container |
 | logLevel | string | `"info"` | Log level for the CNI plugin |
diff --git a/charts/linkerd2-cni/templates/cni-plugin.yaml b/charts/linkerd2-cni/templates/cni-plugin.yaml
index a96c76dd8..afa03ebec 100644
--- a/charts/linkerd2-cni/templates/cni-plugin.yaml
+++ b/charts/linkerd2-cni/templates/cni-plugin.yaml
@@ -214,7 +214,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: {{.Values.cniPluginImage}}:{{.Values.cniPluginVersion}}
+        image: {{ .Values.image.name -}}:{{- .Values.image.version }}
+        imagePullPolicy: {{ .Values.image.pullPolicy }}
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/charts/linkerd2-cni/values.yaml b/charts/linkerd2-cni/values.yaml
index 0c6f89d12..e7029b73b 100644
--- a/charts/linkerd2-cni/values.yaml
+++ b/charts/linkerd2-cni/values.yaml
@@ -10,10 +10,6 @@ ignoreOutboundPorts: ""
 proxyAdminPort: 4191
 # -- Control port for the proxy container
 proxyControlPort: 4190
-# -- Docker image for the CNI plugin
-cniPluginImage:   "cr.l5d.io/linkerd/cni-plugin"
-# -- Tag for the CNI container Docker image
-cniPluginVersion: linkerdVersionValue
 # -- Log level for the CNI plugin
 logLevel:         info
 # -- Ports to redirect to proxy
@@ -48,6 +44,15 @@ tolerations:
 # for more information
 #nodeAffinity:
 
+# -|- Image section
+image:
+  # -- Docker image for the CNI plugin
+  name: "cr.l5d.io/linkerd/cni-plugin"
+  # -- Tag for the CNI container Docker image
+  version: "v1.1.1"
+  # -- Pull policy for the linkerd-cni container
+  pullPolicy: IfNotPresent
+
 #
 ## For Private docker registries, authentication is needed.
 # If the control plane service images are pulled from a
diff --git a/charts/partials/templates/_metadata.tpl b/charts/partials/templates/_metadata.tpl
index e6c75e13b..04d2f1bea 100644
--- a/charts/partials/templates/_metadata.tpl
+++ b/charts/partials/templates/_metadata.tpl
@@ -1,5 +1,5 @@
 {{- define "partials.annotations.created-by" -}}
-linkerd.io/created-by: {{ .Values.cliVersion | default (printf "linkerd/helm %s" (.Values.cniPluginVersion | default .Values.linkerdVersion)) }}
+linkerd.io/created-by: {{ .Values.cliVersion | default (printf "linkerd/helm %s" ( (.Values.image).version | default .Values.linkerdVersion)) }}
 {{- end -}}
 
 {{- define "partials.proxy.annotations" -}}
diff --git a/cli/cmd/install-cni-plugin.go b/cli/cmd/install-cni-plugin.go
index e7932489b..bcbff9bbe 100644
--- a/cli/cmd/install-cni-plugin.go
+++ b/cli/cmd/install-cni-plugin.go
@@ -24,6 +24,12 @@ const (
 	helmCNIDefaultChartDir  = "linkerd2-cni"
 )
 
+type cniPluginImage struct {
+	name       string
+	version    string
+	pullPolicy interface{}
+}
+
 type cniPluginOptions struct {
 	linkerdVersion      string
 	dockerRegistry      string
@@ -35,7 +41,7 @@ type cniPluginOptions struct {
 	ignoreOutboundPorts []string
 	portsToRedirect     []uint
 	proxyUID            int64
-	cniPluginImage      string
+	image               cniPluginImage
 	logLevel            string
 	destCNINetDir       string
 	destCNIBinDir       string
@@ -66,15 +72,22 @@ func (options *cniPluginOptions) validate() error {
 	return nil
 }
 
-func (options *cniPluginOptions) pluginImage() string {
+func (options *cniPluginOptions) pluginImage() cnicharts.Image {
+	image := cnicharts.Image{
+		Name:       options.image.name,
+		Version:    options.image.version,
+		PullPolicy: options.image.pullPolicy,
+	}
 	// env var overrides CLI flag
 	if override := os.Getenv(flags.EnvOverrideDockerRegistry); override != "" {
-		return cmd.RegistryOverride(options.cniPluginImage, override)
+		image.Name = cmd.RegistryOverride(options.image.name, override)
+		return image
 	}
 	if options.dockerRegistry != defaultDockerRegistry {
-		return cmd.RegistryOverride(options.cniPluginImage, options.dockerRegistry)
+		image.Name = cmd.RegistryOverride(options.image.name, options.dockerRegistry)
+		return image
 	}
-	return options.cniPluginImage
+	return image
 }
 
 func newCmdInstallCNIPlugin() *cobra.Command {
@@ -110,7 +123,8 @@ assumes that the 'linkerd install' command will be executed with the
 	cmd.PersistentFlags().StringSliceVar(&options.ignoreInboundPorts, "skip-inbound-ports", options.ignoreInboundPorts, "Ports and/or port ranges (inclusive) that should skip the proxy and send directly to the application")
 	cmd.PersistentFlags().StringSliceVar(&options.ignoreOutboundPorts, "skip-outbound-ports", options.ignoreOutboundPorts, "Outbound ports and/or port ranges (inclusive) that should skip the proxy")
 	cmd.PersistentFlags().UintSliceVar(&options.portsToRedirect, "redirect-ports", options.portsToRedirect, "Ports to redirect to proxy, if no port is specified then ALL ports are redirected")
-	cmd.PersistentFlags().StringVar(&options.cniPluginImage, "cni-image", options.cniPluginImage, "Image for the cni-plugin")
+	cmd.PersistentFlags().StringVar(&options.image.name, "cni-image", options.image.name, "Image for the cni-plugin")
+	cmd.PersistentFlags().StringVar(&options.image.version, "cni-image-version", options.image.version, "Image Version for the cni-plugin")
 	cmd.PersistentFlags().StringVar(&options.logLevel, "cni-log-level", options.logLevel, "Log level for the cni-plugin")
 	cmd.PersistentFlags().StringVar(&options.destCNINetDir, "dest-cni-net-dir", options.destCNINetDir, "Directory on the host where the CNI configuration will be placed")
 	cmd.PersistentFlags().StringVar(&options.destCNIBinDir, "dest-cni-bin-dir", options.destCNIBinDir, "Directory on the host where the CNI binary will be placed")
@@ -129,6 +143,12 @@ func newCNIInstallOptionsWithDefaults() (*cniPluginOptions, error) {
 	if err != nil {
 		return nil, err
 	}
+
+	cniPluginImage := cniPluginImage{
+		name:    defaultDockerRegistry + "/cni-plugin",
+		version: version.LinkerdCNIVersion,
+	}
+
 	cniOptions := cniPluginOptions{
 		linkerdVersion:      version.Version,
 		dockerRegistry:      defaultDockerRegistry,
@@ -139,7 +159,7 @@ func newCNIInstallOptionsWithDefaults() (*cniPluginOptions, error) {
 		ignoreInboundPorts:  nil,
 		ignoreOutboundPorts: nil,
 		proxyUID:            defaults.ProxyUID,
-		cniPluginImage:      defaultDockerRegistry + "/cni-plugin",
+		image:               cniPluginImage,
 		logLevel:            "info",
 		destCNINetDir:       defaults.DestCNINetDir,
 		destCNIBinDir:       defaults.DestCNIBinDir,
@@ -169,8 +189,7 @@ func (options *cniPluginOptions) buildValues() (*cnicharts.Values, error) {
 		portsToRedirect = append(portsToRedirect, fmt.Sprintf("%d", p))
 	}
 
-	installValues.CNIPluginImage = options.pluginImage()
-	installValues.CNIPluginVersion = options.linkerdVersion
+	installValues.Image = options.pluginImage()
 	installValues.LogLevel = options.logLevel
 	installValues.InboundProxyPort = options.inboundPort
 	installValues.OutboundProxyPort = options.outboundPort
diff --git a/jaeger/charts/linkerd-jaeger/Chart.yaml b/jaeger/charts/linkerd-jaeger/Chart.yaml
index 338f33d97..7e0970b4b 100644
--- a/jaeger/charts/linkerd-jaeger/Chart.yaml
+++ b/jaeger/charts/linkerd-jaeger/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.21.0-0"
 name: linkerd-jaeger
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.4.6
+version: 30.4.7
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/jaeger/charts/linkerd-jaeger/README.md b/jaeger/charts/linkerd-jaeger/README.md
index 7ee4aa706..3a811098e 100644
--- a/jaeger/charts/linkerd-jaeger/README.md
+++ b/jaeger/charts/linkerd-jaeger/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Jaeger extension adds distributed tracing to Linkerd using
 OpenCensus and Jaeger.
 
-![Version: 30.4.6](https://img.shields.io/badge/Version-30.4.6-informational?style=flat-square)
+![Version: 30.4.7](https://img.shields.io/badge/Version-30.4.7-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/justfile b/justfile
index 1a253bf7c..7102a47d2 100644
--- a/justfile
+++ b/justfile
@@ -274,6 +274,7 @@ controller-image := DOCKER_REGISTRY + "/controller"
 proxy-image := DOCKER_REGISTRY + "/proxy"
 proxy-init-image := "ghcr.io/linkerd/proxy-init"
 policy-controller-image := DOCKER_REGISTRY + "/policy-controller"
+cni-plugin-image := DOCKER_REGISTRY + "/cni-plugin"
 
 linkerd *flags:
     {{ _linkerd }} {{ flags }}
@@ -312,6 +313,7 @@ linkerd-load: _linkerd-images _k3d-init
         '{{ controller-image }}:{{ linkerd-tag }}' \
         '{{ policy-controller-image }}:{{ linkerd-tag }}' \
         '{{ proxy-image }}:{{ linkerd-tag }}' \
+        "{{ cni-plugin-image }}:$(yq .image.version charts/linkerd2-cni/values.yaml)" \
         "{{ proxy-init-image }}:$(yq .proxyInit.image.version charts/linkerd-control-plane/values.yaml)"
 
 linkerd-build: _policy-controller-build
@@ -323,6 +325,7 @@ _linkerd-images:
     #!/usr/bin/env bash
     set -xeuo pipefail
     docker pull -q "{{ proxy-init-image }}:$(yq .proxyInit.image.version charts/linkerd-control-plane/values.yaml)"
+    docker pull -q "{{ cni-plugin-image }}:$(yq .image.version charts/linkerd2-cni/values.yaml)"
     for img in \
         '{{ controller-image }}:{{ linkerd-tag }}' \
         '{{ policy-controller-image }}:{{ linkerd-tag }}' \
@@ -367,6 +370,7 @@ _linkerd-init: && _linkerd-ready
             controller-image='{{ controller-image }}' \
             proxy-image='{{ proxy-image }}' \
             proxy-init-image='{{ proxy-init-image }}' \
+            cni-plugin-image='{{ cni-plugin-image }}'
             linkerd-exec='{{ linkerd-exec }}' \
             linkerd-install
     fi
diff --git a/multicluster/charts/linkerd-multicluster/Chart.yaml b/multicluster/charts/linkerd-multicluster/Chart.yaml
index 901b8dfc4..d617da619 100644
--- a/multicluster/charts/linkerd-multicluster/Chart.yaml
+++ b/multicluster/charts/linkerd-multicluster/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.21.0-0"
 name: "linkerd-multicluster"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.2.6
+version: 30.2.7
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/multicluster/charts/linkerd-multicluster/README.md b/multicluster/charts/linkerd-multicluster/README.md
index 4f1a513cb..3318edca3 100644
--- a/multicluster/charts/linkerd-multicluster/README.md
+++ b/multicluster/charts/linkerd-multicluster/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Multicluster extension contains resources to support multicluster
 linking to remote clusters
 
-![Version: 30.2.6](https://img.shields.io/badge/Version-30.2.6-informational?style=flat-square)
+![Version: 30.2.7](https://img.shields.io/badge/Version-30.2.7-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/pkg/charts/cni/values.go b/pkg/charts/cni/values.go
index 60093c3e1..fb033b565 100644
--- a/pkg/charts/cni/values.go
+++ b/pkg/charts/cni/values.go
@@ -15,6 +15,13 @@ const (
 	helmDefaultCNIChartDir = "linkerd2-cni"
 )
 
+// Image contains details about the location of the container image
+type Image struct {
+	Name       string      `json:"name"`
+	Version    string      `json:"version"`
+	PullPolicy interface{} `json:"pullPolicy"`
+}
+
 // Values contains the top-level elements in the cni Helm chart
 type Values struct {
 	InboundProxyPort    uint          `json:"inboundProxyPort"`
@@ -22,8 +29,7 @@ type Values struct {
 	IgnoreInboundPorts  string        `json:"ignoreInboundPorts"`
 	IgnoreOutboundPorts string        `json:"ignoreOutboundPorts"`
 	CliVersion          string        `json:"cliVersion"`
-	CNIPluginImage      string        `json:"cniPluginImage"`
-	CNIPluginVersion    string        `json:"cniPluginVersion"`
+	Image               Image         `json:"image"`
 	LogLevel            string        `json:"logLevel"`
 	PortsToRedirect     string        `json:"portsToRedirect"`
 	ProxyUID            int64         `json:"proxyUID"`
diff --git a/pkg/version/version.go b/pkg/version/version.go
index fd404e79f..3132ba1b7 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -16,6 +16,7 @@ var Version = undefinedVersion
 // https://github.com/linkerd/linkerd2-proxy-init This has to be kept in sync
 // with the default version in the control plane's values.yaml.
 var ProxyInitVersion = "v2.0.0"
+var LinkerdCNIVersion = "v1.1.1"
 
 const (
 	// undefinedVersion should take the form `channel-version` to conform to
diff --git a/viz/charts/linkerd-viz/Chart.yaml b/viz/charts/linkerd-viz/Chart.yaml
index e91a7ce9b..9eeb3152f 100644
--- a/viz/charts/linkerd-viz/Chart.yaml
+++ b/viz/charts/linkerd-viz/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.21.0-0"
 name: "linkerd-viz"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.3.6
+version: 30.3.7
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/viz/charts/linkerd-viz/README.md b/viz/charts/linkerd-viz/README.md
index 6d91749b9..9f493c06f 100644
--- a/viz/charts/linkerd-viz/README.md
+++ b/viz/charts/linkerd-viz/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Viz extension contains observability and visualization
 components for Linkerd.
 
-![Version: 30.3.6](https://img.shields.io/badge/Version-30.3.6-informational?style=flat-square)
+![Version: 30.3.7](https://img.shields.io/badge/Version-30.3.7-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 


Test patch:
diff --git a/cli/cmd/install-cni-plugin_test.go b/cli/cmd/install-cni-plugin_test.go
index 5eff53267..d97a6481e 100644
--- a/cli/cmd/install-cni-plugin_test.go
+++ b/cli/cmd/install-cni-plugin_test.go
@@ -12,6 +12,11 @@ func TestRenderCNIPlugin(t *testing.T) {
 		t.Fatalf("Unexpected error from newCNIInstallOptionsWithDefaults(): %v", err)
 	}
 
+	image := cniPluginImage{
+		name:       "my-docker-registry.io/awesome/cni-plugin-test-image",
+		version:    "v1.1.1",
+		pullPolicy: nil,
+	}
 	fullyConfiguredOptions := &cniPluginOptions{
 		linkerdVersion:      "awesome-linkerd-version.1",
 		dockerRegistry:      "cr.l5d.io/linkerd",
@@ -22,7 +27,7 @@ func TestRenderCNIPlugin(t *testing.T) {
 		ignoreInboundPorts:  make([]string, 0),
 		ignoreOutboundPorts: make([]string, 0),
 		proxyUID:            12102,
-		cniPluginImage:      "my-docker-registry.io/awesome/cni-plugin-test-image",
+		image:               image,
 		logLevel:            "debug",
 		destCNINetDir:       "/etc/kubernetes/cni/net.d",
 		destCNIBinDir:       "/opt/my-cni/bin",
@@ -39,7 +44,7 @@ func TestRenderCNIPlugin(t *testing.T) {
 		ignoreInboundPorts:  make([]string, 0),
 		ignoreOutboundPorts: make([]string, 0),
 		proxyUID:            12102,
-		cniPluginImage:      "my-docker-registry.io/awesome/cni-plugin-test-image",
+		image:               image,
 		logLevel:            "debug",
 		destCNINetDir:       "/etc/kubernetes/cni/net.d",
 		destCNIBinDir:       "/etc/kubernetes/cni/net.d",
@@ -56,7 +61,7 @@ func TestRenderCNIPlugin(t *testing.T) {
 		ignoreInboundPorts:  make([]string, 0),
 		ignoreOutboundPorts: make([]string, 0),
 		proxyUID:            12102,
-		cniPluginImage:      "my-docker-registry.io/awesome/cni-plugin-test-image",
+		image:               image,
 		logLevel:            "debug",
 		destCNINetDir:       "/etc/kubernetes/cni/net.d",
 		destCNIBinDir:       "/opt/my-cni/bin",
diff --git a/cli/cmd/install_cni_helm_test.go b/cli/cmd/install_cni_helm_test.go
index 6c9e74e3d..ca654a38a 100644
--- a/cli/cmd/install_cni_helm_test.go
+++ b/cli/cmd/install_cni_helm_test.go
@@ -32,9 +32,11 @@ func TestRenderCniHelm(t *testing.T) {
 			"namespace": "linkerd-test",
   			"inboundProxyPort": 1234,
   			"outboundProxyPort": 5678,
-  			"cniPluginImage": "cr.l5d.io/linkerd/cni-plugin-test",
-  			"cniPluginVersion": "test-version",
   			"logLevel": "debug",
+			"image": {
+				"name": "cr.l5d.io/linkerd/cni-plugin",
+				"version": "v1.1.1"
+			},
   			"proxyUID": 1111,
   			"destCNINetDir": "/etc/cni/net.d-test",
   			"destCNIBinDir": "/opt/cni/bin-test",
diff --git a/cli/cmd/testdata/install-cni-plugin_default.golden b/cli/cmd/testdata/install-cni-plugin_default.golden
index b8a6095d7..5af295bae 100644
--- a/cli/cmd/testdata/install-cni-plugin_default.golden
+++ b/cli/cmd/testdata/install-cni-plugin_default.golden
@@ -115,7 +115,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:dev-undefined
+        image: cr.l5d.io/linkerd/cni-plugin:v1.1.1
+        imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
index ff5a65bb5..f19f080ab 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
@@ -116,7 +116,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: my-docker-registry.io/awesome/cni-plugin-test-image:awesome-linkerd-version.1
+        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.1.1
+        imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
index a0f04f154..54bb9d3ae 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
@@ -116,7 +116,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: my-docker-registry.io/awesome/cni-plugin-test-image:awesome-linkerd-version.1
+        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.1.1
+        imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
index ff5a65bb5..f19f080ab 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
@@ -116,7 +116,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: my-docker-registry.io/awesome/cni-plugin-test-image:awesome-linkerd-version.1
+        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.1.1
+        imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/cli/cmd/testdata/install-cni-plugin_skip_ports.golden b/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
index 74166825e..a6d6a253b 100644
--- a/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
+++ b/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
@@ -116,7 +116,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:dev-undefined
+        image: cr.l5d.io/linkerd/cni-plugin:v1.1.1
+        imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/cli/cmd/testdata/install_cni_helm_default_output.golden b/cli/cmd/testdata/install_cni_helm_default_output.golden
index c886ddb5d..4922f7b4b 100644
--- a/cli/cmd/testdata/install_cni_helm_default_output.golden
+++ b/cli/cmd/testdata/install_cni_helm_default_output.golden
@@ -109,7 +109,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:dev-undefined
+        image: cr.l5d.io/linkerd/cni-plugin:v1.1.1
+        imagePullPolicy: IfNotPresent
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/cli/cmd/testdata/install_cni_helm_override_output.golden b/cli/cmd/testdata/install_cni_helm_override_output.golden
index 1935629ad..e565e50fb 100644
--- a/cli/cmd/testdata/install_cni_helm_override_output.golden
+++ b/cli/cmd/testdata/install_cni_helm_override_output.golden
@@ -110,7 +110,8 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin-test:test-version
+        image: cr.l5d.io/linkerd/cni-plugin:v1.1.1
+        imagePullPolicy: IfNotPresent
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom:
diff --git a/cni-plugin/test/install-cni_test.go b/cni-plugin/test/install-cni_test.go
index 5ceefbf52..1fb57118d 100644
--- a/cni-plugin/test/install-cni_test.go
+++ b/cni-plugin/test/install-cni_test.go
@@ -22,7 +22,6 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"os/user"
 	"strconv"
 	"strings"
 	"testing"
@@ -149,12 +148,7 @@ func populateK8sCreds(wd string, tempK8sSvcAcctDir string, t *testing.T) {
 
 // startDocker starts a test Docker container and runs the install-cni.sh script.
 func startDocker(testNum int, wd string, testWorkRootDir string, tempCNINetDir string, tempCNIBinDir string, tempK8sSvcAcctDir string, t *testing.T) string {
-	// The following is in place to default to a sane development environment that mirrors how bin/fast-build
-	// does it. To change to a different docker image, set the HUB and TAG environment variables before running the tests.
-	gitShaHead, _ := exec.Command("git", "rev-parse", "--short=8", "HEAD").Output()
-	user, _ := user.Current()
-	tag := "dev-" + strings.Trim(string(gitShaHead), "\n") + "-" + user.Username
-	dockerImage := env("HUB", "cr.l5d.io/linkerd") + "/cni-plugin:" + env("TAG", tag)
+	dockerImage := env("HUB", "cr.l5d.io/linkerd") + "/cni-plugin:" + env("CNI_PLUGIN_VERSION", "v1.1.1")
 	errFileName := testWorkRootDir + "/docker_run_stderr"
 
 	// Build arguments list by picking whatever is necessary from the environment.
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index d8a267586..fada5a009 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -2422,7 +2422,7 @@ spec:
       serviceAccountName: linkerd-cni
       containers:
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:git-b4266c93
+        image: cr.l5d.io/linkerd/cni-plugin:v1.1.1
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom: