Repository: linkerd/linkerd2
Pull request #8431 (2022-05-05 18:20:19)

edge-22.5.1
This edge release adds more flexibility to the MeshTLSAuthentication and
AuthorizationPolicy policy resources by allowing them to target entire
namespaces. It also fixes a race condition when multiple CNI plugins are
installed together as well as a number of other bug fixes.

* Added support for MeshTLSAuthentication resources to target an entire
  namespace, authenticating all ServiceAccounts in that namespace
* Fixed a panic in `linkerd install` when the `--ignore-cluster` flag is passed
* Fixed issue where pods would fail to start when `enablePSP` and
  `proxyInit.runAsRoot` are set
* Added support for AuthorizationPolicy resources to target namespaces, applying
  to all Servers in that namespace
* Fixed a race condition where the Linkerd CNI configuration could be overwritten
  when multiple CNI plugins are installed
* Added test for opaque ports using Service and Pod IPs (thanks @krzysztofdrys!)
* Fixed an error in the linkerd-viz Helm chart in HA mode

Signed-off-by: Alex Leong <alex@buoyant.io>

<!--  Thanks for sending a pull request!

If you already have a well-structured git commit message, chances are GitHub
set the title and description of this PR to the git commit message subject and
body, respectively. If so, you may delete these instructions and submit your PR.

If this is your first time, please read our contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md

The title and description of your Pull Request should match the git commit
subject and body, respectively. Git commit messages are structured as follows:

```
Subject

Problem

Solution

Validation

Fixes #[GitHub issue ID]

DCO Sign off
```

Example git commit message:

```
Introduce Pull Request Template

GitHub's community guidelines recommend a pull request template, the repo was
lacking one.

Introduce a `PULL_REQUEST_TEMPLATE.md` file.

Once merged, the
[Community profile checklist](https://github.com/linkerd/linkerd2/community)
should indicate the repo now provides a pull request template.

Fixes #3321

Signed-off-by: Jane Smith <jane.smith@example.com>
```

Note the git commit message subject becomes the pull request title.

For more details around git commits, see the section on Committing in our
contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing
-->



Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 6a32c8047..6f1d958c3 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -118,10 +118,16 @@ issues:
     path: .*test.*
     text: "G204: Subprocess launched with .*"
 
-  # In tests we are usually mocking components or have a good idea about the
-  # errors that we expect. For this reason, we ignore unchecked errors in
-  # all test files.
-  - path: .*test.*\.go
+  # Test/fuzzing do not need to be checked for file handle cleanup.
+  - linters:
+    - gosec
+    path: .*(test|fuzzer).*\.go
+    text: Deferring unsafe method \"Close\" on type \"\*os\.File\"
+
+  # In tests/fuzzing we are usually mocking components or have a good idea
+  # about the errors that we expect. For this reason, we ignore unchecked
+  # errors in all test files.
+  - path: .*(test|fuzzer).*\.go
     text: Error return value of `.*` is not checked
 
   # In tests we'll ignore unchecked filename operations because the values
diff --git a/CHANGES.md b/CHANGES.md
index 42848d840..631a64443 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,24 @@
 # Changes
 
+## edge-22.5.1
+
+This edge release adds more flexibility to the MeshTLSAuthentication and
+AuthorizationPolicy policy resources by allowing them to target entire
+namespaces. It also fixes a race condition when multiple CNI plugins are
+installed together as well as a number of other bug fixes.
+
+* Added support for MeshTLSAuthentication resources to target an entire
+  namespace, authenticating all ServiceAccounts in that namespace
+* Fixed a panic in `linkerd install` when the `--ignore-cluster` flag is passed
+* Fixed issue where pods would fail to start when `enablePSP` and
+  `proxyInit.runAsRoot` are set
+* Added support for AuthorizationPolicy resources to target namespaces, applying
+  to all Servers in that namespace
+* Fixed a race condition where the Linkerd CNI configuration could be
+  overwritten when multiple CNI plugins are installed
+* Added test for opaque ports using Service and Pod IPs (thanks @krzysztofdrys!)
+* Fixed an error in the linkerd-viz Helm chart in HA mode
+
 ## edge-22.4.1
 
 In order to support having custom resources in the default Linkerd installation,
diff --git a/charts/linkerd-control-plane/Chart.yaml b/charts/linkerd-control-plane/Chart.yaml
index 38e28eccc..a4a0e76ea 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.3.0-edge
+version: 1.3.1-edge
 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 d099c3867..d7701e5a9 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.3.0-edge](https://img.shields.io/badge/Version-1.3.0--edge-informational?style=flat-square)
+![Version: 1.3.1-edge](https://img.shields.io/badge/Version-1.3.1--edge-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 ce514f42e..3463ae708 100644
--- a/charts/linkerd2-cni/Chart.yaml
+++ b/charts/linkerd2-cni/Chart.yaml
@@ -9,5 +9,5 @@ description: |
 kubeVersion: ">=1.20.0-0"
 icon: https://linkerd.io/images/logo-only-200h.png
 name: "linkerd2-cni"
-version: 30.1.0-edge
+version: 30.1.1-edge
 
diff --git a/charts/linkerd2-cni/README.md b/charts/linkerd2-cni/README.md
index 0999b7c44..80c203573 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.1.0-edge](https://img.shields.io/badge/Version-30.1.0--edge-informational?style=flat-square)
+![Version: 30.1.1-edge](https://img.shields.io/badge/Version-30.1.1--edge-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/controller/api/destination/destination_fuzzer.go b/controller/api/destination/destination_fuzzer.go
new file mode 100644
index 000000000..ff774763f
--- /dev/null
+++ b/controller/api/destination/destination_fuzzer.go
@@ -0,0 +1,100 @@
+package destination
+
+import (
+	"testing"
+
+	fuzz "github.com/AdaLogics/go-fuzz-headers"
+	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
+	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
+	"github.com/linkerd/linkerd2/controller/api/util"
+	sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
+	logging "github.com/sirupsen/logrus"
+)
+
+func init() {
+	testing.Init()
+}
+
+// FuzzAdd fuzzes the Add method of the destination server.
+func FuzzAdd(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	set := watcher.AddressSet{}
+	err := f.GenerateStruct(&set)
+	if err != nil {
+		return 0
+	}
+	t := &testing.T{}
+	_, translator := makeEndpointTranslator(t)
+	translator.Add(set)
+	translator.Remove(set)
+	return 1
+}
+
+// FuzzGet fuzzes the Get method of the destination server.
+func FuzzGet(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	dest1 := &pb.GetDestination{}
+	err := f.GenerateStruct(dest1)
+	if err != nil {
+		return 0
+	}
+	dest2 := &pb.GetDestination{}
+	err = f.GenerateStruct(dest2)
+	if err != nil {
+		return 0
+	}
+	dest3 := &pb.GetDestination{}
+	err = f.GenerateStruct(dest3)
+	if err != nil {
+		return 0
+	}
+	t := &testing.T{}
+	server := makeServer(t)
+
+	stream := &bufferingGetStream{
+		updates:          []*pb.Update{},
+		MockServerStream: util.NewMockServerStream(),
+	}
+	_ = server.Get(dest1, stream)
+	_ = server.Get(dest2, stream)
+	_ = server.Get(dest3, stream)
+	return 1
+}
+
+// FuzzGetProfile fuzzes the GetProfile method of the destination server.
+func FuzzGetProfile(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	dest := &pb.GetDestination{}
+	err := f.GenerateStruct(dest)
+	if err != nil {
+		return 0
+	}
+	t := &testing.T{}
+	server := makeServer(t)
+	stream := &bufferingGetProfileStream{
+		updates:          []*pb.DestinationProfile{},
+		MockServerStream: util.NewMockServerStream(),
+	}
+	stream.Cancel()
+	_ = server.GetProfile(dest, stream)
+	return 1
+}
+
+// FuzzProfileTranslatorUpdate fuzzes the Update method of the profile translator.
+func FuzzProfileTranslatorUpdate(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	profile := &sp.ServiceProfile{}
+	err := f.GenerateStruct(profile)
+	if err != nil {
+		return 0
+	}
+	t := &testing.T{}
+	mockGetProfileServer := &mockDestinationGetProfileServer{profilesReceived: []*pb.DestinationProfile{}}
+
+	translator := &profileTranslator{
+		stream: mockGetProfileServer,
+		log:    logging.WithField("test", t.Name()),
+	}
+	translator.Update(profile)
+	return 1
+}
diff --git a/go.mod b/go.mod
index 484db3345..2f6aac238 100644
--- a/go.mod
+++ b/go.mod
@@ -55,6 +55,7 @@ require (
 
 require (
 	cloud.google.com/go v0.99.0 // indirect
+	github.com/AdaLogics/go-fuzz-headers v0.0.0-20220408101031-f1761e18c0c6 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest v0.11.20 // indirect
@@ -70,10 +71,11 @@ require (
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/containerd/containerd v1.6.1 // indirect
+	github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/docker/cli v20.10.11+incompatible // indirect
+	github.com/docker/cli v20.10.12+incompatible // indirect
 	github.com/docker/distribution v2.7.1+incompatible // indirect
 	github.com/docker/docker v20.10.12+incompatible // indirect
 	github.com/docker/docker-credential-helpers v0.6.4 // indirect
@@ -94,6 +96,7 @@ require (
 	github.com/google/btree v1.0.1 // indirect
 	github.com/google/gnostic v0.5.7-v3refs // indirect
 	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-containerregistry v0.7.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/google/uuid v1.2.0 // indirect
@@ -131,6 +134,7 @@ require (
 	github.com/shopspring/decimal v1.2.0 // indirect
 	github.com/spf13/cast v1.4.1 // indirect
 	github.com/stretchr/testify v1.7.0 // indirect
+	github.com/vbatts/tar-split v0.11.2 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
diff --git a/go.sum b/go.sum
index 1cd61bc74..f06173ac6 100644
--- a/go.sum
+++ b/go.sum
@@ -53,6 +53,8 @@ contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX
 contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20220408101031-f1761e18c0c6 h1:l+ujeiNp0cAxa7aU5Cw1ufjaxu4FfpuyeW1DQhV21Cc=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20220408101031-f1761e18c0c6/go.mod h1:mHEIrBMD2IgTJphw1R5sfgwN/Xg/q8IPOUlb90leKyk=
 github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
@@ -216,6 +218,9 @@ github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDX
 github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4=
 github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
 github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM=
+github.com/containerd/stargz-snapshotter/estargz v0.10.0/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0=
+github.com/containerd/stargz-snapshotter/estargz v0.10.1 h1:hd1EoVjI2Ax8Cr64tdYqnJ4i4pZU49FkEf5kU8KxQng=
+github.com/containerd/stargz-snapshotter/estargz v0.10.1/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0=
 github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
 github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ=
 github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s=
@@ -265,11 +270,15 @@ github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DB
 github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac=
 github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
 github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v20.10.10+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc=
 github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA=
+github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
 github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U=
 github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
@@ -471,6 +480,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
+github.com/google/go-containerregistry v0.7.0 h1:u0onUUOcyoCDHEiJoyR1R1gx5er1+r06V5DBhUU5ndk=
+github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -778,6 +789,8 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/image-spec v1.0.2-0.20210730191737-8e42a01fb1b7/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/image-spec v1.0.2-0.20210819154149-5ad6f50d6283/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
 github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
@@ -945,6 +958,9 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
+github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
 github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
 github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
 github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
@@ -1092,6 +1108,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1151,6 +1168,7 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -1288,6 +1306,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1507,6 +1526,7 @@ google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEc
 google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211111162719-482062a4217b/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
diff --git a/jaeger/charts/linkerd-jaeger/Chart.yaml b/jaeger/charts/linkerd-jaeger/Chart.yaml
index 094202d08..dabcb0b90 100644
--- a/jaeger/charts/linkerd-jaeger/Chart.yaml
+++ b/jaeger/charts/linkerd-jaeger/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.20.0-0"
 name: linkerd-jaeger
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.3.0-edge
+version: 30.3.1-edge
 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 d70ddd9f5..0bf8c1a27 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.3.0-edge](https://img.shields.io/badge/Version-30.3.0--edge-informational?style=flat-square)
+![Version: 30.3.1-edge](https://img.shields.io/badge/Version-30.3.1--edge-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/multicluster/charts/linkerd-multicluster/Chart.yaml b/multicluster/charts/linkerd-multicluster/Chart.yaml
index 3f7c10219..79e4620df 100644
--- a/multicluster/charts/linkerd-multicluster/Chart.yaml
+++ b/multicluster/charts/linkerd-multicluster/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.20.0-0"
 name: "linkerd-multicluster"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.1.0-edge
+version: 30.1.1-edge
 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 6fdbb0e72..0ebc93daa 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.1.0-edge](https://img.shields.io/badge/Version-30.1.0--edge-informational?style=flat-square)
+![Version: 30.1.1-edge](https://img.shields.io/badge/Version-30.1.1--edge-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/healthcheck/healthcheck_fuzzer.go b/pkg/healthcheck/healthcheck_fuzzer.go
new file mode 100644
index 000000000..724ccdc92
--- /dev/null
+++ b/pkg/healthcheck/healthcheck_fuzzer.go
@@ -0,0 +1,18 @@
+package healthcheck
+
+import (
+	"context"
+
+	"github.com/linkerd/linkerd2/pkg/k8s"
+)
+
+// FuzzFetchCurrentConfiguration fuzzes the FetchCurrentConfiguration function.
+func FuzzFetchCurrentConfiguration(data []byte) int {
+	clientset, err := k8s.NewFakeAPI(string(data))
+	if err != nil {
+		return 0
+	}
+
+	_, _, _ = FetchCurrentConfiguration(context.Background(), clientset, "linkerd")
+	return 1
+}
diff --git a/pkg/identity/identity_fuzzer.go b/pkg/identity/identity_fuzzer.go
new file mode 100644
index 000000000..69d9a1b83
--- /dev/null
+++ b/pkg/identity/identity_fuzzer.go
@@ -0,0 +1,26 @@
+package identity
+
+import (
+	"context"
+
+	pb "github.com/linkerd/linkerd2-proxy-api/go/identity"
+	"github.com/linkerd/linkerd2/pkg/tls"
+
+	fuzz "github.com/AdaLogics/go-fuzz-headers"
+)
+
+// FuzzServiceCertify fuzzes the Service.Certify method.
+func FuzzServiceCertify(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	req := &pb.CertifyRequest{}
+	err := f.GenerateStruct(req)
+	if err != nil {
+		return 0
+	}
+
+	svc := NewService(&fakeValidator{"successful-result", nil}, nil, nil, nil, "", "", "")
+	svc.updateIssuer(&fakeIssuer{tls.Crt{}, nil})
+
+	_, _ = svc.Certify(context.Background(), req)
+	return 1
+}
diff --git a/pkg/inject/inject_fuzzer.go b/pkg/inject/inject_fuzzer.go
new file mode 100644
index 000000000..ede129e96
--- /dev/null
+++ b/pkg/inject/inject_fuzzer.go
@@ -0,0 +1,37 @@
+package inject
+
+import (
+	l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
+
+	fuzz "github.com/AdaLogics/go-fuzz-headers"
+)
+
+// FuzzInject fuzzes Pod injection.
+func FuzzInject(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	yamlBytes, err := f.GetBytes()
+	if err != nil {
+		return 0
+	}
+
+	v := &l5dcharts.Values{}
+	err = f.GenerateStruct(v)
+	if err != nil {
+		return 0
+	}
+	conf := NewResourceConfig(v, OriginUnknown, "")
+	_, _ = conf.ParseMetaAndYAML(yamlBytes)
+	injectProxy, err := f.GetBool()
+	if err != nil {
+		return 0
+	}
+	_, _ = conf.GetPodPatch(injectProxy)
+	_, _ = conf.CreateOpaquePortsPatch()
+
+	report := &Report{}
+	err = f.GenerateStruct(report)
+	if err == nil {
+		_, _ = conf.Uninject(report)
+	}
+	return 1
+}
diff --git a/pkg/profiles/profiles_fuzzer.go b/pkg/profiles/profiles_fuzzer.go
new file mode 100644
index 000000000..c2e38539e
--- /dev/null
+++ b/pkg/profiles/profiles_fuzzer.go
@@ -0,0 +1,52 @@
+package profiles
+
+import (
+	"os"
+
+	fuzz "github.com/AdaLogics/go-fuzz-headers"
+)
+
+// FuzzProfilesValidate fuzzes the ProfilesValidate function.
+func FuzzProfilesValidate(data []byte) int {
+	_ = Validate(data)
+	return 1
+}
+
+// FuzzRenderProto fuzzes the RenderProto function.
+func FuzzRenderProto(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	protodata, err := f.GetBytes()
+	if err != nil {
+		return 0
+	}
+	namespace, err := f.GetString()
+	if err != nil {
+		return 0
+	}
+	name, err := f.GetString()
+	if err != nil {
+		return 0
+	}
+	clusterDomain, err := f.GetString()
+	if err != nil {
+		return 0
+	}
+	protofile, err := os.Create("protofile")
+	if err != nil {
+		return 0
+	}
+	defer protofile.Close()
+	defer os.Remove(protofile.Name())
+
+	_, err = protofile.Write(protodata)
+	if err != nil {
+		return 0
+	}
+	w, err := os.OpenFile("/dev/null", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
+	if err != nil {
+		return 0
+	}
+	defer w.Close()
+	_ = RenderProto(protofile.Name(), namespace, name, clusterDomain, w)
+	return 1
+}
diff --git a/viz/charts/linkerd-viz/Chart.yaml b/viz/charts/linkerd-viz/Chart.yaml
index a711cddec..be121cd84 100644
--- a/viz/charts/linkerd-viz/Chart.yaml
+++ b/viz/charts/linkerd-viz/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.20.0-0"
 name: "linkerd-viz"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.2.0-edge
+version: 30.2.1-edge
 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 4931eb1c7..72ed2834a 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.2.0-edge](https://img.shields.io/badge/Version-30.2.0--edge-informational?style=flat-square)
+![Version: 30.2.1-edge](https://img.shields.io/badge/Version-30.2.1--edge-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/endpoints_test.go b/cli/cmd/endpoints_test.go
index badc0ffd4..28acfba77 100644
--- a/cli/cmd/endpoints_test.go
+++ b/cli/cmd/endpoints_test.go
@@ -4,13 +4,13 @@ import (
 	"testing"
 
 	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
-	"github.com/linkerd/linkerd2/controller/api/destination"
+	"github.com/linkerd/linkerd2/controller/api/util"
 )
 
 type endpointsExp struct {
 	options     *endpointsOptions
 	authorities []string
-	endpoints   []destination.AuthorityEndpoints
+	endpoints   []util.AuthorityEndpoints
 	file        string
 }
 
@@ -21,11 +21,11 @@ func TestEndpoints(t *testing.T) {
 		testEndpointsCall(endpointsExp{
 			options:     options,
 			authorities: []string{"emoji-svc.emojivoto.svc.cluster.local:8080", "voting-svc.emojivoto.svc.cluster.local:8080"},
-			endpoints: []destination.AuthorityEndpoints{
+			endpoints: []util.AuthorityEndpoints{
 				{
 					Namespace: "emojivoto",
 					ServiceID: "emoji-svc",
-					Pods: []destination.PodDetails{
+					Pods: []util.PodDetails{
 						{
 							Name: "emoji-6bf9f47bd5-jjcrl",
 							IP:   16909060,
@@ -36,7 +36,7 @@ func TestEndpoints(t *testing.T) {
 				{
 					Namespace: "emojivoto",
 					ServiceID: "voting-svc",
-					Pods: []destination.PodDetails{
+					Pods: []util.PodDetails{
 						{
 							Name: "voting-7bf9f47bd5-jjdrl",
 							IP:   84281096,
@@ -53,11 +53,11 @@ func TestEndpoints(t *testing.T) {
 		testEndpointsCall(endpointsExp{
 			options:     options,
 			authorities: []string{"emoji-svc.emojivoto.svc.cluster.local:8080", "voting-svc.emojivoto2.svc.cluster.local:8080"},
-			endpoints: []destination.AuthorityEndpoints{
+			endpoints: []util.AuthorityEndpoints{
 				{
 					Namespace: "emojivoto",
 					ServiceID: "emoji-svc",
-					Pods: []destination.PodDetails{
+					Pods: []util.PodDetails{
 						{
 							Name: "emoji-6bf9f47bd5-jjcrl",
 							IP:   16909060,
@@ -68,7 +68,7 @@ func TestEndpoints(t *testing.T) {
 				{
 					Namespace: "emojivoto2",
 					ServiceID: "voting-svc",
-					Pods: []destination.PodDetails{
+					Pods: []util.PodDetails{
 						{
 							Name: "voting-7bf9f47bd5-jjdrl",
 							IP:   84281096,
@@ -86,11 +86,11 @@ func TestEndpoints(t *testing.T) {
 		testEndpointsCall(endpointsExp{
 			options:     options,
 			authorities: []string{"emoji-svc.emojivoto.svc.cluster.local:8080", "voting-svc.emojivoto.svc.cluster.local:8080"},
-			endpoints: []destination.AuthorityEndpoints{
+			endpoints: []util.AuthorityEndpoints{
 				{
 					Namespace: "emojivoto",
 					ServiceID: "emoji-svc",
-					Pods: []destination.PodDetails{
+					Pods: []util.PodDetails{
 						{
 							Name: "emoji-6bf9f47bd5-jjcrl",
 							IP:   16909060,
@@ -101,7 +101,7 @@ func TestEndpoints(t *testing.T) {
 				{
 					Namespace: "emojivoto",
 					ServiceID: "voting-svc",
-					Pods: []destination.PodDetails{
+					Pods: []util.PodDetails{
 						{
 							Name: "voting-7bf9f47bd5-jjdrl",
 							IP:   84281096,
@@ -118,12 +118,12 @@ func TestEndpoints(t *testing.T) {
 func testEndpointsCall(exp endpointsExp, t *testing.T) {
 	updates := make([]pb.Update, 0)
 	for _, endpoint := range exp.endpoints {
-		addrSet := destination.BuildAddrSet(endpoint)
+		addrSet := util.BuildAddrSet(endpoint)
 		updates = append(updates, pb.Update{Update: &pb.Update_Add{Add: addrSet}})
 	}
 
-	mockClient := &destination.MockAPIClient{
-		DestinationGetClientToReturn: &destination.MockDestinationGetClient{
+	mockClient := &util.MockAPIClient{
+		DestinationGetClientToReturn: &util.MockDestinationGetClient{
 			UpdatesToReturn: updates,
 		},
 	}
diff --git a/controller/api/destination/endpoint_translator_test.go b/controller/api/destination/endpoint_translator_test.go
index ce81d17c2..d59ef3044 100644
--- a/controller/api/destination/endpoint_translator_test.go
+++ b/controller/api/destination/endpoint_translator_test.go
@@ -10,10 +10,8 @@ import (
 	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
 	"github.com/linkerd/linkerd2-proxy-api/go/net"
 	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
-	pkgk8s "github.com/linkerd/linkerd2/controller/k8s"
 	"github.com/linkerd/linkerd2/pkg/addr"
 	"github.com/linkerd/linkerd2/pkg/k8s"
-	logging "github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/api/discovery/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -104,45 +102,6 @@ var (
 	}
 )
 
-func makeEndpointTranslator(t *testing.T) (*mockDestinationGetServer, *endpointTranslator) {
-	k8sAPI, err := pkgk8s.NewFakeAPI(`
-apiVersion: v1
-kind: Node
-metadata:
-  annotations:
-    kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
-    node.alpha.kubernetes.io/ttl: "0"
-  labels:
-    beta.kubernetes.io/arch: amd64
-    kubernetes.io/os: linux
-    kubernetes.io/arch: amd64
-    kubernetes.io/hostname: kind-worker
-    kubernetes.io/os: linux
-    topology.kubernetes.io/region: west
-    topology.kubernetes.io/zone: west-1a
-  name: test-123
-`,
-	)
-	if err != nil {
-		t.Fatalf("NewFakeAPI returned an error: %s", err)
-	}
-	k8sAPI.Sync(nil)
-
-	mockGetServer := &mockDestinationGetServer{updatesReceived: []*pb.Update{}}
-	translator := newEndpointTranslator(
-		"linkerd",
-		"trust.domain",
-		true,
-		"service-name.service-ns",
-		"test-123",
-		map[uint32]struct{}{},
-		k8sAPI.Node(),
-		mockGetServer,
-		logging.WithField("test", t.Name()),
-	)
-	return mockGetServer, translator
-}
-
 func TestEndpointTranslatorForRemoteGateways(t *testing.T) {
 	t.Run("Sends one update for add and another for remove", func(t *testing.T) {
 		mockGetServer, translator := makeEndpointTranslator(t)
diff --git a/controller/api/destination/server_test.go b/controller/api/destination/server_test.go
index f6d7153d0..8ec4789ab 100644
--- a/controller/api/destination/server_test.go
+++ b/controller/api/destination/server_test.go
@@ -32,401 +32,6 @@ const port uint32 = 8989
 const opaquePort uint32 = 4242
 const skippedPort uint32 = 24224
 
-type mockDestinationGetServer struct {
-	util.MockServerStream
-	updatesReceived []*pb.Update
-}
-
-func (m *mockDestinationGetServer) Send(update *pb.Update) error {
-	m.updatesReceived = append(m.updatesReceived, update)
-	return nil
-}
-
-type mockDestinationGetProfileServer struct {
-	util.MockServerStream
-	profilesReceived []*pb.DestinationProfile
-}
-
-func (m *mockDestinationGetProfileServer) Send(profile *pb.DestinationProfile) error {
-	m.profilesReceived = append(m.profilesReceived, profile)
-	return nil
-}
-
-func makeServer(t *testing.T) *server {
-	meshedPodResources := []string{`
-apiVersion: v1
-kind: Namespace
-metadata:
-  name: ns`,
-		`
-apiVersion: v1
-kind: Service
-metadata:
-  name: name1
-  namespace: ns
-spec:
-  type: LoadBalancer
-  clusterIP: 172.17.12.0
-  ports:
-  - port: 8989`,
-		`
-apiVersion: v1
-kind: Endpoints
-metadata:
-  name: name1
-  namespace: ns
-subsets:
-- addresses:
-  - ip: 172.17.0.12
-    targetRef:
-      kind: Pod
-      name: name1-1
-      namespace: ns
-  ports:
-  - port: 8989`,
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  labels:
-    linkerd.io/control-plane-ns: linkerd
-  name: name1-1
-  namespace: ns
-status:
-  phase: Running
-  podIP: 172.17.0.12
-spec:
-  containers:
-    - env:
-      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
-        value: 0.0.0.0:4143
-      name: linkerd-proxy`,
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  name: name2-2
-  namespace: ns
-status:
-  phase: Succeeded
-  podIP: 172.17.0.13`,
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  name: name2-3
-  namespace: ns
-status:
-  phase: Failed
-  podIP: 172.17.0.13`,
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  name: name2-4
-  namespace: ns
-  deletionTimestamp: 2021-01-01T00:00:00Z
-status:
-  podIP: 172.17.0.13`,
-		`
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: name1.ns.svc.mycluster.local
-  namespace: ns
-spec:
-  routes:
-  - name: route1
-    isRetryable: false
-    condition:
-      pathRegex: "/a/b/c"`,
-	}
-
-	clientSP := []string{
-		`
-apiVersion: linkerd.io/v1alpha2
-kind: ServiceProfile
-metadata:
-  name: name1.ns.svc.mycluster.local
-  namespace: client-ns
-spec:
-  routes:
-  - name: route2
-    isRetryable: true
-    condition:
-      pathRegex: "/x/y/z"`,
-	}
-
-	unmeshedPod := `
-apiVersion: v1
-kind: Pod
-metadata:
-  name: name2
-  namespace: ns
-status:
-  phase: Running
-  podIP: 172.17.0.13`
-
-	meshedOpaquePodResources := []string{
-		`
-apiVersion: v1
-kind: Service
-metadata:
-  name: name3
-  namespace: ns
-spec:
-  type: LoadBalancer
-  clusterIP: 172.17.12.1
-  ports:
-  - port: 4242`,
-		`
-apiVersion: v1
-kind: Endpoints
-metadata:
-  name: name3
-  namespace: ns
-subsets:
-- addresses:
-  - ip: 172.17.0.14
-    targetRef:
-      kind: Pod
-      name: name3
-      namespace: ns
-  ports:
-  - port: 4242`,
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  labels:
-    linkerd.io/control-plane-ns: linkerd
-  annotations:
-    config.linkerd.io/opaque-ports: "4242"
-  name: name3
-  namespace: ns
-status:
-  phase: Running
-  podIP: 172.17.0.14
-spec:
-  containers:
-    - env:
-      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
-        value: 0.0.0.0:4143
-      name: linkerd-proxy`,
-	}
-
-	meshedOpaqueServiceResources := []string{
-		`
-apiVersion: v1
-kind: Service
-metadata:
-  name: name4
-  namespace: ns
-  annotations:
-    config.linkerd.io/opaque-ports: "4242"`,
-	}
-
-	meshedSkippedPodResource := []string{
-		`
-apiVersion: v1
-kind: Service
-metadata:
-  name: name5
-  namespace: ns
-spec:
-  type: LoadBalancer
-  clusterIP: 172.17.13.1
-  ports:
-  - port: 24224`,
-		`
-apiVersion: v1
-kind: Endpoints
-metadata:
-  name: name5
-  namespace: ns
-subsets:
-- addresses:
-  - ip: 172.17.0.15
-    targetRef:
-      kind: Pod
-      name: name5
-      namespace: ns
-  ports:
-  - port: 24224`,
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  labels:
-    linkerd.io/control-plane-ns: linkerd
-  annotations:
-    config.linkerd.io/skip-inbound-ports: "24224"
-  name: name5
-  namespace: ns
-status:
-  phase: Running
-  podIP: 172.17.0.15
-spec:
-  containers:
-    - env:
-      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
-        value: 0.0.0.0:4143
-      name: linkerd-proxy`,
-	}
-
-	meshedStatefulSetPodResource := []string{
-		`
-apiVersion: v1
-kind: Service
-metadata:
-  name: statefulset-svc
-  namespace: ns
-spec:
-  type: LoadBalancer
-  clusterIP: 172.17.13.5
-  ports:
-  - port: 8989`,
-		`
-apiVersion: v1
-kind: Endpoints
-metadata:
-  name:	statefulset-svc
-  namespace: ns
-subsets:
-- addresses:
-  - ip: 172.17.13.15
-    hostname: pod-0
-    targetRef:
-      kind: Pod
-      name: pod-0
-      namespace: ns
-  ports:
-  - port: 8989`,
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  labels:
-    linkerd.io/control-plane-ns: linkerd
-  name: pod-0
-  namespace: ns
-status:
-  phase: Running
-  podIP: 172.17.13.15`,
-	}
-
-	policyResources := []string{
-		`
-apiVersion: v1
-kind: Pod
-metadata:
-  labels:
-    linkerd.io/control-plane-ns: linkerd
-    app: policy-test
-  name: pod-policyResources
-  namespace: ns
-status:
-  phase: Running
-  podIP: 172.17.0.16
-spec:
-  containers:
-    - name: linkerd-proxy
-      env:
-      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
-        value: 0.0.0.0:4143
-    - name: app
-      image: nginx
-      ports:
-      - containerPort: 80
-        name: http
-        protocol: TCP`,
-		`
-apiVersion: policy.linkerd.io/v1beta1
-kind: Server
-metadata:
-  name: srv
-  namespace: ns
-spec:
-  podSelector:
-    matchLabels:
-      app: policy-test
-  port: 80
-  proxyProtocol: opaque`,
-	}
-
-	res := append(meshedPodResources, clientSP...)
-	res = append(res, unmeshedPod)
-	res = append(res, meshedOpaquePodResources...)
-	res = append(res, meshedOpaqueServiceResources...)
-	res = append(res, meshedSkippedPodResource...)
-	res = append(res, meshedStatefulSetPodResource...)
-	res = append(res, policyResources...)
-	k8sAPI, err := k8s.NewFakeAPI(res...)
-	if err != nil {
-		t.Fatalf("NewFakeAPI returned an error: %s", err)
-	}
-	log := logging.WithField("test", t.Name())
-	defaultOpaquePorts := map[uint32]struct{}{
-		25:    {},
-		443:   {},
-		587:   {},
-		3306:  {},
-		5432:  {},
-		11211: {},
-	}
-
-	err = watcher.InitializeIndexers(k8sAPI)
-	if err != nil {
-		t.Fatalf("initializeIndexers returned an error: %s", err)
-	}
-
-	endpoints := watcher.NewEndpointsWatcher(k8sAPI, log, false)
-	opaquePorts := watcher.NewOpaquePortsWatcher(k8sAPI, log, defaultOpaquePorts)
-	profiles := watcher.NewProfileWatcher(k8sAPI, log)
-	servers := watcher.NewServerWatcher(k8sAPI, log)
-
-	// Sync after creating watchers so that the the indexers added get updated
-	// properly
-	k8sAPI.Sync(nil)
-
-	return &server{
-		pb.UnimplementedDestinationServer{},
-		endpoints,
-		opaquePorts,
-		profiles,
-		servers,
-		k8sAPI.Node(),
-		true,
-		"linkerd",
-		"trust.domain",
-		"mycluster.local",
-		defaultOpaquePorts,
-		k8sAPI,
-		log,
-		make(<-chan struct{}),
-	}
-}
-
-type bufferingGetStream struct {
-	updates []*pb.Update
-	util.MockServerStream
-}
-
-func (bgs *bufferingGetStream) Send(update *pb.Update) error {
-	bgs.updates = append(bgs.updates, update)
-	return nil
-}
-
-type bufferingGetProfileStream struct {
-	updates []*pb.DestinationProfile
-	util.MockServerStream
-}
-
-func (bgps *bufferingGetProfileStream) Send(profile *pb.DestinationProfile) error {
-	bgps.updates = append(bgps.updates, profile)
-	return nil
-}
-
 func TestGet(t *testing.T) {
 	t.Run("Returns error if not valid service name", func(t *testing.T) {
 		server := makeServer(t)
diff --git a/controller/api/destination/test_util.go b/controller/api/destination/test_util.go
new file mode 100644
index 000000000..e447402bc
--- /dev/null
+++ b/controller/api/destination/test_util.go
@@ -0,0 +1,447 @@
+package destination
+
+import (
+	"testing"
+
+	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
+	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
+	"github.com/linkerd/linkerd2/controller/api/util"
+	"github.com/linkerd/linkerd2/controller/k8s"
+	pkgk8s "github.com/linkerd/linkerd2/controller/k8s"
+	"github.com/sirupsen/logrus"
+	logging "github.com/sirupsen/logrus"
+)
+
+func makeServer(t *testing.T) *server {
+	meshedPodResources := []string{`
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: ns`,
+		`
+apiVersion: v1
+kind: Service
+metadata:
+  name: name1
+  namespace: ns
+spec:
+  type: LoadBalancer
+  clusterIP: 172.17.12.0
+  ports:
+  - port: 8989`,
+		`
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: name1
+  namespace: ns
+subsets:
+- addresses:
+  - ip: 172.17.0.12
+    targetRef:
+      kind: Pod
+      name: name1-1
+      namespace: ns
+  ports:
+  - port: 8989`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  labels:
+    linkerd.io/control-plane-ns: linkerd
+  name: name1-1
+  namespace: ns
+status:
+  phase: Running
+  podIP: 172.17.0.12
+spec:
+  containers:
+    - env:
+      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
+        value: 0.0.0.0:4143
+      name: linkerd-proxy`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  name: name2-2
+  namespace: ns
+status:
+  phase: Succeeded
+  podIP: 172.17.0.13`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  name: name2-3
+  namespace: ns
+status:
+  phase: Failed
+  podIP: 172.17.0.13`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  name: name2-4
+  namespace: ns
+  deletionTimestamp: 2021-01-01T00:00:00Z
+status:
+  podIP: 172.17.0.13`,
+		`
+apiVersion: linkerd.io/v1alpha2
+kind: ServiceProfile
+metadata:
+  name: name1.ns.svc.mycluster.local
+  namespace: ns
+spec:
+  routes:
+  - name: route1
+    isRetryable: false
+    condition:
+      pathRegex: "/a/b/c"`,
+	}
+
+	clientSP := []string{
+		`
+apiVersion: linkerd.io/v1alpha2
+kind: ServiceProfile
+metadata:
+  name: name1.ns.svc.mycluster.local
+  namespace: client-ns
+spec:
+  routes:
+  - name: route2
+    isRetryable: true
+    condition:
+      pathRegex: "/x/y/z"`,
+	}
+
+	unmeshedPod := `
+apiVersion: v1
+kind: Pod
+metadata:
+  name: name2
+  namespace: ns
+status:
+  phase: Running
+  podIP: 172.17.0.13`
+
+	meshedOpaquePodResources := []string{
+		`
+apiVersion: v1
+kind: Service
+metadata:
+  name: name3
+  namespace: ns
+spec:
+  type: LoadBalancer
+  clusterIP: 172.17.12.1
+  ports:
+  - port: 4242`,
+		`
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: name3
+  namespace: ns
+subsets:
+- addresses:
+  - ip: 172.17.0.14
+    targetRef:
+      kind: Pod
+      name: name3
+      namespace: ns
+  ports:
+  - port: 4242`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  labels:
+    linkerd.io/control-plane-ns: linkerd
+  annotations:
+    config.linkerd.io/opaque-ports: "4242"
+  name: name3
+  namespace: ns
+status:
+  phase: Running
+  podIP: 172.17.0.14
+spec:
+  containers:
+    - env:
+      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
+        value: 0.0.0.0:4143
+      name: linkerd-proxy`,
+	}
+
+	meshedOpaqueServiceResources := []string{
+		`
+apiVersion: v1
+kind: Service
+metadata:
+  name: name4
+  namespace: ns
+  annotations:
+    config.linkerd.io/opaque-ports: "4242"`,
+	}
+
+	meshedSkippedPodResource := []string{
+		`
+apiVersion: v1
+kind: Service
+metadata:
+  name: name5
+  namespace: ns
+spec:
+  type: LoadBalancer
+  clusterIP: 172.17.13.1
+  ports:
+  - port: 24224`,
+		`
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: name5
+  namespace: ns
+subsets:
+- addresses:
+  - ip: 172.17.0.15
+    targetRef:
+      kind: Pod
+      name: name5
+      namespace: ns
+  ports:
+  - port: 24224`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  labels:
+    linkerd.io/control-plane-ns: linkerd
+  annotations:
+    config.linkerd.io/skip-inbound-ports: "24224"
+  name: name5
+  namespace: ns
+status:
+  phase: Running
+  podIP: 172.17.0.15
+spec:
+  containers:
+    - env:
+      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
+        value: 0.0.0.0:4143
+      name: linkerd-proxy`,
+	}
+
+	meshedStatefulSetPodResource := []string{
+		`
+apiVersion: v1
+kind: Service
+metadata:
+  name: statefulset-svc
+  namespace: ns
+spec:
+  type: LoadBalancer
+  clusterIP: 172.17.13.5
+  ports:
+  - port: 8989`,
+		`
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name:	statefulset-svc
+  namespace: ns
+subsets:
+- addresses:
+  - ip: 172.17.13.15
+    hostname: pod-0
+    targetRef:
+      kind: Pod
+      name: pod-0
+      namespace: ns
+  ports:
+  - port: 8989`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  labels:
+    linkerd.io/control-plane-ns: linkerd
+  name: pod-0
+  namespace: ns
+status:
+  phase: Running
+  podIP: 172.17.13.15`,
+	}
+
+	policyResources := []string{
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  labels:
+    linkerd.io/control-plane-ns: linkerd
+    app: policy-test
+  name: pod-policyResources
+  namespace: ns
+status:
+  phase: Running
+  podIP: 172.17.0.16
+spec:
+  containers:
+    - name: linkerd-proxy
+      env:
+      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
+        value: 0.0.0.0:4143
+    - name: app
+      image: nginx
+      ports:
+      - containerPort: 80
+        name: http
+        protocol: TCP`,
+		`
+apiVersion: policy.linkerd.io/v1beta1
+kind: Server
+metadata:
+  name: srv
+  namespace: ns
+spec:
+  podSelector:
+    matchLabels:
+      app: policy-test
+  port: 80
+  proxyProtocol: opaque`,
+	}
+
+	res := append(meshedPodResources, clientSP...)
+	res = append(res, unmeshedPod)
+	res = append(res, meshedOpaquePodResources...)
+	res = append(res, meshedOpaqueServiceResources...)
+	res = append(res, meshedSkippedPodResource...)
+	res = append(res, meshedStatefulSetPodResource...)
+	res = append(res, policyResources...)
+	k8sAPI, err := k8s.NewFakeAPI(res...)
+	if err != nil {
+		t.Fatalf("NewFakeAPI returned an error: %s", err)
+	}
+	log := logging.WithField("test", t.Name())
+	defaultOpaquePorts := map[uint32]struct{}{
+		25:    {},
+		443:   {},
+		587:   {},
+		3306:  {},
+		5432:  {},
+		11211: {},
+	}
+
+	err = watcher.InitializeIndexers(k8sAPI)
+	if err != nil {
+		t.Fatalf("initializeIndexers returned an error: %s", err)
+	}
+
+	endpoints := watcher.NewEndpointsWatcher(k8sAPI, log, false)
+	opaquePorts := watcher.NewOpaquePortsWatcher(k8sAPI, log, defaultOpaquePorts)
+	profiles := watcher.NewProfileWatcher(k8sAPI, log)
+	servers := watcher.NewServerWatcher(k8sAPI, log)
+
+	// Sync after creating watchers so that the the indexers added get updated
+	// properly
+	k8sAPI.Sync(nil)
+
+	return &server{
+		pb.UnimplementedDestinationServer{},
+		endpoints,
+		opaquePorts,
+		profiles,
+		servers,
+		k8sAPI.Node(),
+		true,
+		"linkerd",
+		"trust.domain",
+		"mycluster.local",
+		defaultOpaquePorts,
+		k8sAPI,
+		log,
+		make(<-chan struct{}),
+	}
+}
+
+type bufferingGetStream struct {
+	updates []*pb.Update
+	util.MockServerStream
+}
+
+func (bgs *bufferingGetStream) Send(update *pb.Update) error {
+	bgs.updates = append(bgs.updates, update)
+	return nil
+}
+
+type bufferingGetProfileStream struct {
+	updates []*pb.DestinationProfile
+	util.MockServerStream
+}
+
+func (bgps *bufferingGetProfileStream) Send(profile *pb.DestinationProfile) error {
+	bgps.updates = append(bgps.updates, profile)
+	return nil
+}
+
+type mockDestinationGetServer struct {
+	util.MockServerStream
+	updatesReceived []*pb.Update
+}
+
+func (m *mockDestinationGetServer) Send(update *pb.Update) error {
+	m.updatesReceived = append(m.updatesReceived, update)
+	return nil
+}
+
+type mockDestinationGetProfileServer struct {
+	util.MockServerStream
+	profilesReceived []*pb.DestinationProfile
+}
+
+func (m *mockDestinationGetProfileServer) Send(profile *pb.DestinationProfile) error {
+	m.profilesReceived = append(m.profilesReceived, profile)
+	return nil
+}
+
+func makeEndpointTranslator(t *testing.T) (*mockDestinationGetServer, *endpointTranslator) {
+	k8sAPI, err := pkgk8s.NewFakeAPI(`
+apiVersion: v1
+kind: Node
+metadata:
+  annotations:
+    kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
+    node.alpha.kubernetes.io/ttl: "0"
+  labels:
+    beta.kubernetes.io/arch: amd64
+    kubernetes.io/os: linux
+    kubernetes.io/arch: amd64
+    kubernetes.io/hostname: kind-worker
+    kubernetes.io/os: linux
+    topology.kubernetes.io/region: west
+    topology.kubernetes.io/zone: west-1a
+  name: test-123
+`,
+	)
+	if err != nil {
+		t.Fatalf("NewFakeAPI returned an error: %s", err)
+	}
+	k8sAPI.Sync(nil)
+
+	mockGetServer := &mockDestinationGetServer{updatesReceived: []*pb.Update{}}
+	translator := newEndpointTranslator(
+		"linkerd",
+		"trust.domain",
+		true,
+		"service-name.service-ns",
+		"test-123",
+		map[uint32]struct{}{},
+		k8sAPI.Node(),
+		mockGetServer,
+		logrus.WithField("test", t.Name()),
+	)
+	return mockGetServer, translator
+}
diff --git a/controller/api/util/test_helper.go b/controller/api/util/test_helper.go
deleted file mode 100644
index b03567487..000000000
--- a/controller/api/util/test_helper.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package util
-
-import (
-	"golang.org/x/net/context"
-	"google.golang.org/grpc/metadata"
-)
-
-type mockStream struct {
-	ctx    context.Context
-	Cancel context.CancelFunc
-}
-
-func newMockStream() mockStream {
-	ctx, cancel := context.WithCancel(context.Background())
-	return mockStream{ctx, cancel}
-}
-
-func (ms mockStream) Context() context.Context    { return ms.ctx }
-func (ms mockStream) SendMsg(m interface{}) error { return nil }
-func (ms mockStream) RecvMsg(m interface{}) error { return nil }
-
-// MockServerStream satisfies the grpc.ServerStream interface
-type MockServerStream struct{ mockStream }
-
-// SetHeader satisfies the grpc.ServerStream interface
-func (mss MockServerStream) SetHeader(metadata.MD) error { return nil }
-
-// SendHeader satisfies the grpc.ServerStream interface
-func (mss MockServerStream) SendHeader(metadata.MD) error { return nil }
-
-// SetTrailer satisfies the grpc.ServerStream interface
-func (mss MockServerStream) SetTrailer(metadata.MD) {}
-
-// NewMockServerStream instantiates a MockServerStream
-func NewMockServerStream() MockServerStream {
-	return MockServerStream{newMockStream()}
-}
diff --git a/controller/api/destination/test_helper.go b/controller/api/util/test_util.go
similarity index 71%
rename from controller/api/destination/test_helper.go
rename to controller/api/util/test_util.go
index 3d86713be..905e3b9fc 100644
--- a/controller/api/destination/test_helper.go
+++ b/controller/api/util/test_util.go
@@ -1,16 +1,48 @@
-package destination
+package util
 
 import (
-	"context"
 	"errors"
 	"io"
 	"sync"
 
 	destinationPb "github.com/linkerd/linkerd2-proxy-api/go/destination"
 	"github.com/linkerd/linkerd2-proxy-api/go/net"
+	"golang.org/x/net/context"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/metadata"
 )
 
+type mockStream struct {
+	ctx    context.Context
+	Cancel context.CancelFunc
+}
+
+func newMockStream() mockStream {
+	ctx, cancel := context.WithCancel(context.Background())
+	return mockStream{ctx, cancel}
+}
+
+func (ms mockStream) Context() context.Context    { return ms.ctx }
+func (ms mockStream) SendMsg(m interface{}) error { return nil }
+func (ms mockStream) RecvMsg(m interface{}) error { return nil }
+
+// MockServerStream satisfies the grpc.ServerStream interface
+type MockServerStream struct{ mockStream }
+
+// SetHeader satisfies the grpc.ServerStream interface
+func (mss MockServerStream) SetHeader(metadata.MD) error { return nil }
+
+// SendHeader satisfies the grpc.ServerStream interface
+func (mss MockServerStream) SendHeader(metadata.MD) error { return nil }
+
+// SetTrailer satisfies the grpc.ServerStream interface
+func (mss MockServerStream) SetTrailer(metadata.MD) {}
+
+// NewMockServerStream instantiates a MockServerStream
+func NewMockServerStream() MockServerStream {
+	return MockServerStream{newMockStream()}
+}
+
 // MockAPIClient satisfies the destination API's interfaces
 type MockAPIClient struct {
 	ErrorToReturn                error
diff --git a/pkg/identity/service_test.go b/pkg/identity/service_test.go
index 5465b2e70..4c37e1140 100644
--- a/pkg/identity/service_test.go
+++ b/pkg/identity/service_test.go
@@ -2,31 +2,12 @@ package identity
 
 import (
 	"context"
-	"crypto/x509"
 	"testing"
 
 	pb "github.com/linkerd/linkerd2-proxy-api/go/identity"
 	"github.com/linkerd/linkerd2/pkg/tls"
 )
 
-type fakeValidator struct {
-	result string
-	err    error
-}
-
-type fakeIssuer struct {
-	result tls.Crt
-	err    error
-}
-
-func (fi *fakeIssuer) IssueEndEntityCrt(*x509.CertificateRequest) (tls.Crt, error) {
-	return fi.result, fi.err
-}
-
-func (fk *fakeValidator) Validate(context.Context, []byte) (string, error) {
-	return fk.result, fk.err
-}
-
 func TestServiceNotReady(t *testing.T) {
 	// ch := make(chan tls.Issuer, 1)
 	svc := NewService(&fakeValidator{"successful-result", nil}, nil, nil, nil, "", "", "")
diff --git a/pkg/identity/test_util.go b/pkg/identity/test_util.go
new file mode 100644
index 000000000..941d8dc3d
--- /dev/null
+++ b/pkg/identity/test_util.go
@@ -0,0 +1,26 @@
+package identity
+
+import (
+	"context"
+	"crypto/x509"
+
+	"github.com/linkerd/linkerd2/pkg/tls"
+)
+
+type fakeValidator struct {
+	result string
+	err    error
+}
+
+func (fk *fakeValidator) Validate(context.Context, []byte) (string, error) {
+	return fk.result, fk.err
+}
+
+type fakeIssuer struct {
+	result tls.Crt
+	err    error
+}
+
+func (fi *fakeIssuer) IssueEndEntityCrt(*x509.CertificateRequest) (tls.Crt, error) {
+	return fi.result, fi.err
+}
diff --git a/test/fuzzing/README.md b/test/fuzzing/README.md
new file mode 100644
index 000000000..c1a9f67aa
--- /dev/null
+++ b/test/fuzzing/README.md
@@ -0,0 +1,26 @@
+# Fuzzing Linkerd
+
+The scripting setup for fuzzing is used by
+[google/oss-fuzz](https://github.com/google/oss-fuzz) which performs continuous
+fuzzing for the Linkerd project.
+
+The fuzzing configuration for Linkerd is located in the
+[linkerd2](https://github.com/google/oss-fuzz/tree/master/projects/linkerd2)
+project which handles the docker build and execution of the fuzzers.
+
+## Running locally
+Instructions for running the fuzzers locally can be found in the oss-fuzz
+[docs](https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally).
+
+This will require cloning the [oss-fuzz](https://github.com/google/oss-fuzz)
+repository locally and running the commands outlined in the instructions.
+
+### oss-fuzz File Setup
+
+- [Dockerfile](https://github.com/google/oss-fuzz/blob/master/projects/linkerd2/Dockerfile)
+  provides the necessary environment for running the fuzzer; the main thing
+  being the `oss-fuzz-base` image which provides the `compile_go_fuzzer`
+  funtions seen in this directory's `build.sh`.
+- [build.sh](https://github.com/google/oss-fuzz/blob/master/projects/linkerd2/build.sh)
+  is responsible for calling the fuzzing functions for each fuzzer in the
+  linkerd2 project.
diff --git a/test/fuzzing/fuzzers.go b/test/fuzzing/fuzzers.go
new file mode 100644
index 000000000..b0d0a96b8
--- /dev/null
+++ b/test/fuzzing/fuzzers.go
@@ -0,0 +1,53 @@
+package fuzzing
+
+import (
+	fuzz "github.com/AdaLogics/go-fuzz-headers"
+	"github.com/linkerd/linkerd2/pkg/healthcheck"
+	"github.com/linkerd/linkerd2/pkg/util"
+	corev1 "k8s.io/api/core/v1"
+)
+
+// FuzzParsePorts fuzzes the ParsePorts function.
+func FuzzParsePorts(data []byte) int {
+	_, _ = util.ParsePorts(string(data))
+	return 1
+}
+
+// FuzzParseContainerOpaquePorts fuzzes the ParseContainerOpaquePorts function.
+func FuzzParseContainerOpaquePorts(data []byte) int {
+	f := fuzz.NewConsumer(data)
+
+	qtyOfContainers, err := f.GetInt()
+	if err != nil {
+		return 0
+	}
+	qtyOfContainers %= 20
+
+	containers := make([]corev1.Container, 0)
+	for i := 0; i < qtyOfContainers; i++ {
+		newContainer := corev1.Container{}
+		err = f.GenerateStruct(&newContainer)
+		if err != nil {
+			return 0
+		}
+		containers = append(containers, newContainer)
+	}
+	override, err := f.GetString()
+	if err != nil {
+		return 0
+	}
+	_ = util.ParseContainerOpaquePorts(override, containers)
+	return 1
+}
+
+// FuzzHealthCheck fuzzes the HealthCheck method for the healthchecker.
+func FuzzHealthCheck(data []byte) int {
+	f := fuzz.NewConsumer(data)
+	options := &healthcheck.Options{}
+	err := f.GenerateStruct(options)
+	if err != nil {
+		return 0
+	}
+	_ = healthcheck.NewHealthChecker([]healthcheck.CategoryID{healthcheck.KubernetesAPIChecks}, options)
+	return 1
+}
diff --git a/testutil/test_helper.go b/testutil/test_helper.go
index 56db44521..cf6461432 100644
--- a/testutil/test_helper.go
+++ b/testutil/test_helper.go
@@ -724,8 +724,6 @@ func (h *TestHelper) DownloadCLIBinary(filepath, version string) error {
 	if err != nil {
 		return err
 	}
-	// This is a test and we don't care about errors that can occur here.
-	//nolint:gosec
 	defer out.Close()
 
 	_, err = io.Copy(out, resp.Body)