Repository: linkerd/linkerd2
Pull request #5573 (2021-01-19 20:01:32)

Introduce OpenAPIV3 validation for CRDs
## Changes

Sorry I was late with this, it was a bit challenging to figure out what needs to be done (and how). Apologies in advance for the spam, lots of output from tests, will try to keep this short since there is also a lot of yaml to review. Based on the discussion from #5484 in order to migrate CRDs to `v1` they should have a *structural* schema associated. This is asserted throughout official docs pertaining to CRDs:

> A structural schema for CRDs in apiextensions.k8s.io/v1beta1 will not be required. But we plan to require structural schemas for every CRD created in apiextensions.k8s.io/v1

> With apiextensions.k8s.io/v1 the definition of a structural schema is mandatory for CustomResourceDefinitions. In the beta version of CustomResourceDefinition, the structural schema was optional.

I will leave some prior art links at the bottom. In short, a schema is structural if it does not contain any non-empty `type` fields aside from a few exceptions (such as x-kubernetes-int-or-string). There also one more thing to say before I go deeper, according to the [docs](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning) when it coms to legacy CRDS (created as v1beta1) _pruning_ is not enabled and you can store arbitrary data -- to "migrate", it is specified that `spec.preserveUnknownFields` should be set to false. However, `preserveUnknownFields` can still be present as a field in the schema and it will still be structural. [This page has more details](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#controlling-pruning). I'm only mentioning this because you'll encounter the use of `preserveUnknownFields` in the `ServiceProfile` schema and it confused me when I first saw it.

**Changes**:

* Add validation to `TrafficSplit`
* Add validation to `Link`
* Add validation to `ServiceProfile` using kube-gen(ish)
* Validation is top level for all three CRDs

### ServiceProfiles

I tried to use `controller-gen` to re-generate the CRDs, but it kept giving a lot of `{}`. Setting a field type `{}` makes the schema nonstructural, in cases such as these, when inspecting the crd (definition), you will see something similar to:
```
  - lastTransitionTime: "2021-01-19T13:55:13Z"
    message: 'spec.versions[0].schema.openAPIV3Schema.type: Required value: must not
      be empty at the root'
    reason: Violations
    status: "True"
    type: NonStructuralSchema
```

I also tried `kubebuilder` (even though it uses controller-gen) and went down a very deep rabbit hole, not much worked so I decided to just change everything manually. I made the changes by looking at the type definitions in `controller/gen/.../types.go`. For objects such as 
* `ResponseMatch` that contain themselves (recursive types?) I did not really know how to write them and so I declared the types as `object` and added the `x-kube...-preserve-unknown-fields`. This means we can write any arbitrary blob in there but it will not work with pruning as far as I understand. 
* For ints or string I used an appropriate `x-kubernetes-int-or-string` set to true 
* For `opaquePorts` (map[ui32)struct{}) I wrote as a string array since I saw in the code somewhere it was being parsed as a string. Let me know if I should change this.
* For float32s I used `number` with a float format.

#### Test
---
I followed the [debugging-per-route-metrics tutorial](https://linkerd.io/2/tasks/books/#service-profiles). Created a booksapp service then some SPs to test the validation didn't break anything.

```sh
$ curl -sL https://run.linkerd.io/booksapp/webapp.swagger \
  | target/cli/darwin/linkerd -n booksapp profile --open-api - webapp \
  | k -n booksapp apply -f -
serviceprofile.linkerd.io/webapp.booksapp.svc.cluster.local created

$ kubectl get (..)
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  annotations:
spec:
  routes:
  - condition:
      method: GET
      pathRegex: /
    name: GET /
  - condition:
      method: POST
      pathRegex: /authors
    name: POST /authors
  - condition:
      method: GET
      pathRegex: /authors/[^/]*
    name: GET /authors/{id}
....

# Add more SPs -- authors.swagger and books.swagger

$ curl -sL https://run.linkerd.io/booksapp/authors.swagger \
    | target/cli/darwin/linkerd -n booksapp profile --open-api - authors \
    | kubectl -n booksapp apply -f -
serviceprofile.linkerd.io/authors.booksapp.svc.cluster.local created
$  curl -sL https://run.linkerd.io/booksapp/books.swagger \
    | target/cli/darwin/linkerd -n booksapp profile --open-api - books \
    | kubectl -n booksapp apply -f -
serviceprofile.linkerd.io/books.booksapp.svc.cluster.local created

# Configure timeouts and check all is in order

# Before
ROUTE                     SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
DELETE /books/{id}.json     books   100.00%   0.6rps           8ms          10ms          10ms
GET /books.json             books   100.00%   1.2rps           3ms           9ms          10ms
GET /books/{id}.json        books   100.00%   2.2rps           3ms           5ms          18ms
POST /books.json            books    50.69%   2.4rps          10ms          20ms          28ms
PUT /books/{id}.json        books    64.52%   1.0rps          58ms          96ms          99ms
[DEFAULT]                   books         -        -             -             -             -

kubectl -n booksapp edit sp/books.booksapp.svc.cluster.local
serviceprofile.linkerd.io/books.booksapp.svc.cluster.local edited # added 25ms timeout

# After
ROUTE                     SERVICE   EFFECTIVE_SUCCESS   EFFECTIVE_RPS   ACTUAL_SUCCESS   ACTUAL_RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
DELETE /books/{id}.json     books             100.00%          0.6rps          100.00%       0.6rps           7ms          10ms          10ms
GET /books.json             books             100.00%          1.2rps          100.00%       1.2rps           4ms           7ms           9ms
GET /books/{id}.json        books             100.00%          2.7rps          100.00%       2.7rps           3ms           5ms          17ms
POST /books.json            books              48.92%          2.3rps           48.92%       2.3rps           9ms          19ms          20ms
PUT /books/{id}.json        books              36.96%          1.5rps           36.96%       1.5rps          47ms          92ms          98ms
[DEFAULT]                   books                   -               -                -            -             -             -             -

# EFFECTIVE SUCCESS DROPPED
```

---

### TrafficSplits

* TrafficSplits were easy, I just copied the validation from smi-sdk-client-go. I replaced the link at the top to point to a different commit sha with the updated definition. One thing to note here is that the controller & destination crash with the latest version `v1alpha3` since we are using `v1alpha1` informers. Because of this I deleted both v1alpha2 and alpha3 versions from the spec -- in that sense it differs from the smi one. I'm okay to raise an issue to update the code to use recent informers if you'd like.

#### Test
---

I followed the inject faults tutorial to make sure all is in order

```sh
# Before

NAME      MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
authors      1/1   100.00%   7.3rps           3ms          23ms          33ms          6
books        1/1   100.00%   8.9rps           5ms          72ms          94ms          6
traffic      1/1         -        -             -             -             -          -
webapp       3/3   100.00%   8.3rps          16ms          70ms          94ms          9

# First try, apply this:
apiVersion: split.smi-spec.io/v1alpha1
kind: TrafficSplit
metadata:
  name: error-split
  namespace: booksapp
spec:
  service: books
  backends:
  - service: books
    weight: 900m
  - service: error-injector
    weight: 100m —> validation error :( no strings just number (according to smi-spec)

# changed weights to 90 and 10 respectively (number type)

# Resulting routes command after applying -- SUCCESS dropped 10%
ROUTE       SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
[DEFAULT]     books    90.93%   6.8rps           5ms         151ms         190ms
```

---

### Link

* I had no controller-gen or smi-spec here but there aren't many fields and they aren't complicated. I made the changes.

#### Test
---

* I did not get to test it works, just that installation works and we can create a link through cli. I don't see why it wouldn't work if it can be successfully generated as a resource by the cli -- not changing information in any way just validating.

```
$ target/cli/darwin/linkerd mc install | k apply -f -
namespace/linkerd-multicluster created
configmap/linkerd-gateway-config created
...
customresourcedefinition.apiextensions.k8s.io/links.multicluster.linkerd.io created

$ target/../linkerd mc link —cluster-name east | k apply -f -
$ kubectl get crd (..) -o yaml
apiVersion: multicluster.linkerd.io/v1alpha1
kind: Link
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
...
spec:
  clusterCredentialsSecret: cluster-credentials-east
  gatewayAddress: 172.22.0.2
  gatewayIdentity: linkerd-gateway.linkerd-multicluster.serviceaccount.identity.linkerd.cluster.local
  gatewayPort: "4143"
  probeSpec:
    path: /health
    period: 3s
    port: "4181"
  selector:
    matchExpressions:
    - key: mirror.linkerd.io/exported
      operator: Exists
  targetClusterDomain: cluster.local
  targetClusterLinkerdNamespace: linkerd
  targetClusterName: east
```

---

Hopefully after this, we can upgrade all resources to `v1` and end #5484. I mostly managed to answer all the questions I had, I think it would have been easier to just set the `spec.preserveUnknownFields` to true, but I think at some point this would have had to be done anyway? Let me know if I overlooked anything or if this isn't really needed 🤒 

*Prior art*:
 * https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
 * https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#controlling-pruning
 * https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/
 * https://kubernetes.io/blog/2019/06/20/crd-structural-schema/
 * [smi-spec-crd](https://github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml)
 * [apiextension-types.go](https://github.com/kubernetes/apiextensions-apiserver/blob/master/pkg/apis/apiextensions/v1/types.go) -- although it's not in there, having `schema` under validation field is considered top-level. Otherwise we'd have to have it under each specific version. This is not documented anywhere (and not really common sense, it's not top level per se, so I'm 50/50 sure about this). Please let me know if you have a different take on it.

*Note*: it is very probable I made some mistakes and I wasn't thorough enough with the tests, if you could please double check it all works as expected that'd be a huge relief.

Signed-off-by: Matei David <matei.david.35@gmail.com>



olix0r: It looks like this test is failing:

```
--- FAIL: TestTrafficSplitCli (1.08s)
196
    trafficsplit_test.go:160: 'kubectl apply' command failed
197
        deployment.apps/backend created
198
        service/backend-svc created
199
        deployment.apps/failing created
200
        service/failing-svc created
201
        deployment.apps/slow-cooker created
202
        service/slow-cooker created
203
        error: error validating "STDIN": error validating data: [ValidationError(TrafficSplit.spec.backends[0].weight): invalid type for io.smi-spec.split.v1alpha1.TrafficSplit.spec.backends.weight: got "string", expected "number", ValidationError(TrafficSplit.spec.backends[1].weight): invalid type for io.smi-spec.split.v1alpha1.TrafficSplit.spec.backends.weight: got "string", expected "number"]; if you choose to ignore these errors, turn validation off with --validate=false
```
alpeb: @adleong I think for the time being we'll have to leave DOCKER_TRACE disabled until we figure what's going on with the runners. Otherwise CI will fail.
adleong: Thanks for the explanation, @alpeb! :+1:


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e1af959fd..b573b6e91 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -39,7 +39,7 @@ jobs:
       uses: crazy-max/ghaction-docker-buildx@552c9de
     - name: Build & Push Multi Arch Images
       env:
-        DOCKER_TRACE: 1
+        #DOCKER_TRACE: 1
         DOCKER_MULTIARCH: 1
         DOCKER_PUSH: 1
       run: |
diff --git a/.proxy-version b/.proxy-version
index 2d54b0604..64c5512ba 100644
--- a/.proxy-version
+++ b/.proxy-version
@@ -1,1 +1,1 @@
-v2.128.0
+v2.129.0
diff --git a/BUILD.md b/BUILD.md
index 7497568e2..fa059741e 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -121,7 +121,7 @@ Started](https://linkerd.io/2/getting-started/)
 bin/k3d cluster create
 
 # build all docker images
-DOCKER_TRACE=1 bin/docker-build
+bin/docker-build
 
 # load all the images into k3d
 bin/image-load --k3d
@@ -431,7 +431,7 @@ The `bin/docker-build-proxy` script builds the proxy by pulling a pre-published
 proxy binary:
 
 ```bash
-DOCKER_TRACE=1 bin/docker-build-proxy
+bin/docker-build-proxy
 ```
 
 ### Multi-architecture builds
diff --git a/bin/_docker.sh b/bin/_docker.sh
index e10f023f2..6481f288f 100644
--- a/bin/_docker.sh
+++ b/bin/_docker.sh
@@ -11,9 +11,6 @@ bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
 # docker registry in, for instance, CI.
 export DOCKER_REGISTRY=${DOCKER_REGISTRY:-ghcr.io/linkerd}
 
-# When set, causes docker's build output to be emitted to stderr.
-export DOCKER_TRACE=${DOCKER_TRACE:-}
-
 # When set, use `docker buildx` and use the github actions cache to store/retrieve images
 export DOCKER_BUILDKIT=${DOCKER_BUILDKIT:-}
 
@@ -50,11 +47,6 @@ docker_build() {
     file=$1
     shift
 
-    output=/dev/null
-    if [ -n "$DOCKER_TRACE" ]; then
-        output=/dev/stderr
-    fi
-
     rootdir=$( cd "$bindir"/.. && pwd )
 
     if [ -n "$DOCKER_BUILDKIT" ]; then
@@ -82,15 +74,13 @@ docker_build() {
           $output_params \
           -t "$repo:$tag" \
           -f "$file" \
-          "$@" \
-          > "$output"
+          "$@"
     else
       log_debug "  :; docker build $rootdir -t $repo:$tag -f $file $*"
       docker build "$rootdir" \
           -t "$repo:$tag" \
           -f "$file" \
-          "$@" \
-          > "$output"
+          "$@"
     fi
 
     echo "$repo:$tag"
diff --git a/charts/linkerd2/templates/serviceprofile-crd.yaml b/charts/linkerd2/templates/serviceprofile-crd.yaml
index 27bdc3fd4..8eaaa48ba 100644
--- a/charts/linkerd2/templates/serviceprofile-crd.yaml
+++ b/charts/linkerd2/templates/serviceprofile-crd.yaml
@@ -21,6 +21,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
diff --git a/charts/linkerd2/templates/trafficsplit-crd.yaml b/charts/linkerd2/templates/trafficsplit-crd.yaml
index f89b0db2e..c843bb937 100644
--- a/charts/linkerd2/templates/trafficsplit-crd.yaml
+++ b/charts/linkerd2/templates/trafficsplit-crd.yaml
@@ -1,7 +1,7 @@
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -14,14 +14,61 @@ metadata:
     {{.Values.global.controllerNamespaceLabel}}: {{.Values.global.namespace}}
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/charts/partials/templates/_proxy.tpl b/charts/partials/templates/_proxy.tpl
index a32063144..e752cd884 100644
--- a/charts/partials/templates/_proxy.tpl
+++ b/charts/partials/templates/_proxy.tpl
@@ -90,13 +90,6 @@ env:
 - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
   value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
 {{ end -}}
-{{ if .Values.global.proxy.disableTap -}}
-- name: LINKERD2_PROXY_TAP_DISABLED
-  value: "true"
-{{ else if not .Values.global.proxy.disableIdentity -}}
-- name: LINKERD2_PROXY_TAP_SVC_NAME
-  value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-{{ end -}}
 image: {{.Values.global.proxy.image.name}}:{{.Values.global.proxy.image.version}}
 imagePullPolicy: {{.Values.global.proxy.image.pullPolicy}}
 livenessProbe:
diff --git a/controller/cmd/main.go b/controller/cmd/main.go
index 4de76cdd2..23fac22f5 100644
--- a/controller/cmd/main.go
+++ b/controller/cmd/main.go
@@ -11,6 +11,7 @@ import (
 	publicapi "github.com/linkerd/linkerd2/controller/cmd/public-api"
 	spvalidator "github.com/linkerd/linkerd2/controller/cmd/sp-validator"
 	"github.com/linkerd/linkerd2/controller/cmd/tap"
+	tapinjector "github.com/linkerd/linkerd2/controller/cmd/tap-injector"
 	servicemirror "github.com/linkerd/linkerd2/multicluster/cmd/service-mirror"
 )
 
@@ -35,6 +36,8 @@ func main() {
 		spvalidator.Main(os.Args[2:])
 	case "tap":
 		tap.Main(os.Args[2:])
+	case "tap-injector":
+		tapinjector.Main(os.Args[2:])
 	case "service-mirror":
 		servicemirror.Main(os.Args[2:])
 	default:
diff --git a/controller/cmd/tap-injector/main.go b/controller/cmd/tap-injector/main.go
new file mode 100644
index 000000000..0a6ca7841
--- /dev/null
+++ b/controller/cmd/tap-injector/main.go
@@ -0,0 +1,31 @@
+package tapinjector
+
+import (
+	"context"
+	"flag"
+	"fmt"
+
+	"github.com/linkerd/linkerd2/controller/k8s"
+	tapinjector "github.com/linkerd/linkerd2/controller/tap-injector"
+	"github.com/linkerd/linkerd2/controller/webhook"
+	"github.com/linkerd/linkerd2/pkg/flags"
+)
+
+// Main executes the tap-injector subcommand
+func Main(args []string) {
+	cmd := flag.NewFlagSet("tap-injector", flag.ExitOnError)
+	metricsAddr := cmd.String("metrics-addr", fmt.Sprintf(":%d", 9995), "address to serve scrapable metrics on")
+	addr := cmd.String("addr", ":8443", "address to serve on")
+	kubeconfig := cmd.String("kubeconfig", "", "path to kubeconfig")
+	tapSvcName := cmd.String("tap-service-name", "", "name of the tap service")
+	flags.ConfigureAndParse(cmd, args)
+	webhook.Launch(
+		context.Background(),
+		[]k8s.APIResource{k8s.NS},
+		tapinjector.Mutate(*tapSvcName),
+		"tap-injector",
+		*metricsAddr,
+		*addr,
+		*kubeconfig,
+	)
+}
diff --git a/controller/proxy-injector/fake/data/pod-with-debug.patch.json b/controller/proxy-injector/fake/data/pod-with-debug.patch.json
index 04c79ec3d..61ba6cc64 100644
--- a/controller/proxy-injector/fake/data/pod-with-debug.patch.json
+++ b/controller/proxy-injector/fake/data/pod-with-debug.patch.json
@@ -183,10 +183,6 @@
         {
           "name": "LINKERD2_PROXY_IDENTITY_DISABLED",
           "value": "disabled"
-        },
-        {
-          "name": "LINKERD2_PROXY_TAP_DISABLED",
-          "value": "true"
         }
       ],
       "image": "ghcr.io/linkerd/proxy:dev-undefined",
diff --git a/controller/proxy-injector/fake/data/pod.patch.json b/controller/proxy-injector/fake/data/pod.patch.json
index 039fc5720..38a93e2e1 100644
--- a/controller/proxy-injector/fake/data/pod.patch.json
+++ b/controller/proxy-injector/fake/data/pod.patch.json
@@ -173,10 +173,6 @@
         {
           "name": "LINKERD2_PROXY_IDENTITY_DISABLED",
           "value": "disabled"
-        },
-        {
-          "name": "LINKERD2_PROXY_TAP_DISABLED",
-          "value": "true"
         }
       ],
       "image": "ghcr.io/linkerd/proxy:dev-undefined",
diff --git a/controller/tap-injector/patch.go b/controller/tap-injector/patch.go
new file mode 100644
index 000000000..86a481bfd
--- /dev/null
+++ b/controller/tap-injector/patch.go
@@ -0,0 +1,12 @@
+package tapinjector
+
+const tpl = `[
+  {
+    "op": "add",
+    "path": "/spec/containers/{{.ProxyIndex}}/env/-",
+    "value": {
+      "name": "LINKERD2_PROXY_TAP_SVC_NAME",
+      "value": "{{.ProxyTapSvcName}}"
+    }
+  }
+]`
diff --git a/controller/tap-injector/webhook.go b/controller/tap-injector/webhook.go
new file mode 100644
index 000000000..3f31fa05a
--- /dev/null
+++ b/controller/tap-injector/webhook.go
@@ -0,0 +1,83 @@
+package tapinjector
+
+import (
+	"bytes"
+	"context"
+	"html/template"
+
+	"github.com/ghodss/yaml"
+	"github.com/linkerd/linkerd2/controller/k8s"
+	"github.com/linkerd/linkerd2/controller/webhook"
+	labels "github.com/linkerd/linkerd2/pkg/k8s"
+	"github.com/prometheus/common/log"
+	admissionv1beta1 "k8s.io/api/admission/v1beta1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/client-go/tools/record"
+)
+
+// Params holds the values used in the patch template.
+type Params struct {
+	ProxyIndex      int
+	ProxyTapSvcName string
+}
+
+// Mutate mutates an AdmissionRequest and adds the LINKERD2_PROXY_TAP_SVC_NAME
+// env var to a pod's proxy container if tap is not disabled via annotation on the
+// pod or the namespace.
+func Mutate(tapSvcName string) webhook.Handler {
+	return func(
+		ctx context.Context,
+		k8sAPI *k8s.API,
+		request *admissionv1beta1.AdmissionRequest,
+		recorder record.EventRecorder,
+	) (*admissionv1beta1.AdmissionResponse, error) {
+		log.Debugf("request object bytes: %s", request.Object.Raw)
+		admissionResponse := &admissionv1beta1.AdmissionResponse{
+			UID:     request.UID,
+			Allowed: true,
+		}
+		var pod *corev1.Pod
+		if err := yaml.Unmarshal(request.Object.Raw, &pod); err != nil {
+			return nil, err
+		}
+		params := Params{
+			ProxyIndex:      webhook.GetProxyContainerIndex(pod.Spec.Containers),
+			ProxyTapSvcName: tapSvcName,
+		}
+		if params.ProxyIndex < 0 {
+			return admissionResponse, nil
+		}
+		if alreadyMutated(pod.Spec.Containers[params.ProxyIndex]) {
+			return admissionResponse, nil
+		}
+		namespace, err := k8sAPI.NS().Lister().Get(request.Namespace)
+		if err != nil {
+			return nil, err
+		}
+		var t *template.Template
+		if labels.IsTapDisabled(namespace) || labels.IsTapDisabled(pod) {
+			return admissionResponse, nil
+		}
+		t, err = template.New("tpl").Parse(tpl)
+		if err != nil {
+			return nil, err
+		}
+		var patchJSON bytes.Buffer
+		if err = t.Execute(&patchJSON, params); err != nil {
+			return nil, err
+		}
+		patchType := admissionv1beta1.PatchTypeJSONPatch
+		admissionResponse.Patch = patchJSON.Bytes()
+		admissionResponse.PatchType = &patchType
+		return admissionResponse, nil
+	}
+}
+
+func alreadyMutated(container corev1.Container) bool {
+	for _, envVar := range container.Env {
+		if envVar.Name == "LINKERD2_PROXY_TAP_SVC_NAME" {
+			return true
+		}
+	}
+	return false
+}
diff --git a/controller/webhook/util.go b/controller/webhook/util.go
new file mode 100644
index 000000000..c456072ef
--- /dev/null
+++ b/controller/webhook/util.go
@@ -0,0 +1,17 @@
+package webhook
+
+import (
+	labels "github.com/linkerd/linkerd2/pkg/k8s"
+	corev1 "k8s.io/api/core/v1"
+)
+
+// GetProxyContainerIndex gets the proxy container index of a pod; the index
+// is required in webhooks because of how patches are created.
+func GetProxyContainerIndex(containers []corev1.Container) int {
+	for i, c := range containers {
+		if c.Name == labels.ProxyContainerName {
+			return i
+		}
+	}
+	return -1
+}
diff --git a/jaeger/injector/mutator/webhook.go b/jaeger/injector/mutator/webhook.go
index 0ea475df0..fbf27ecf9 100644
--- a/jaeger/injector/mutator/webhook.go
+++ b/jaeger/injector/mutator/webhook.go
@@ -56,7 +56,7 @@ func Mutate(collectorSvcAddr, collectorSvcAccount string) webhook.Handler {
 		}
 
 		params := Params{
-			ProxyIndex:          getProxyContainerIndex(pod.Spec.Containers),
+			ProxyIndex:          webhook.GetProxyContainerIndex(pod.Spec.Containers),
 			CollectorSvcAddr:    collectorSvcAddr,
 			CollectorSvcAccount: collectorSvcAccount,
 		}
@@ -88,15 +88,6 @@ func Mutate(collectorSvcAddr, collectorSvcAccount string) webhook.Handler {
 	}
 }
 
-func getProxyContainerIndex(containers []corev1.Container) int {
-	for i, c := range containers {
-		if c.Name == labels.ProxyContainerName {
-			return i
-		}
-	}
-	return -1
-}
-
 func alreadyMutated(pod *corev1.Pod, proxyIndex int) bool {
 	for _, v := range pod.Spec.Volumes {
 		if v.DownwardAPI != nil && v.Name == "podinfo" {
diff --git a/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml b/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml
index c2efffa95..eb3fa6776 100644
--- a/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml
+++ b/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml
@@ -20,3 +20,65 @@ spec:
     plural: links
     singular: link
     kind: Link
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          properties:
+            clusterCredentialsSecret:
+              description: Kubernetes secret of target cluster
+              type: string
+            gatewayAddress:
+              description: Gateway address of target cluster
+              type: string
+            gatewayIdentity:
+              description: Gateway Identity FQDN
+              type: string
+            gatewayPort:
+              description: Gateway Port
+              type: string
+            probeSpec:
+              description: Spec for gateway health probe
+              type: object
+              properties:
+                path:
+                  description: Path of remote gateway health endpoint
+                  type: string
+                period:
+                  description: Interval in between probe requests
+                  type: string
+                port:
+                  description: Port of remote gateway health endpoint
+                  type: string
+            selector:
+              description: Kubernetes Label Selector
+              type: object
+              properties:
+                matchExpressions:
+                  description: List of selector requirements
+                  type: array
+                  items:
+                    description: A selector item requires a key and an operator
+                    type: object
+                    required:
+                    - key
+                    - operator
+                    properties:
+                      key:
+                        description: Label key that selector should apply to
+                        type: string
+                      operator:
+                        description: Evaluation of a label in relation to set
+                        type: string
+            targetClusterName:
+              description: Name of target cluster to link to
+              type: string
+            targetDomainName:
+              description: Domain name of target cluster to link to
+              type: string
+            targetClusterLinkerdNamespace:
+              description: Name of namespace Linkerd control plane is installed in on target cluster
+              type: string
+
diff --git a/pkg/k8s/labels.go b/pkg/k8s/labels.go
index 160242dc1..b44e55e2a 100644
--- a/pkg/k8s/labels.go
+++ b/pkg/k8s/labels.go
@@ -478,10 +478,17 @@ func IsMeshed(pod *corev1.Pod, controllerNS string) bool {
 	return pod.Labels[ControllerNSLabel] == controllerNS
 }
 
-// IsTapDisabled returns true if the pod has an annotation for explicitly
-// disabling tap
-func IsTapDisabled(pod *corev1.Pod) bool {
-	if valStr := pod.Annotations[ProxyDisableTapAnnotation]; valStr != "" {
+// IsTapDisabled returns true if a namespace or pod has an annotation for
+// explicitly disabling tap
+func IsTapDisabled(obj interface{}) bool {
+	var valStr string
+	switch resource := obj.(type) {
+	case *corev1.Pod:
+		valStr = resource.GetAnnotations()[ProxyDisableTapAnnotation]
+	case *corev1.Namespace:
+		valStr = resource.GetAnnotations()[ProxyDisableTapAnnotation]
+	}
+	if valStr != "" {
 		valBool, err := strconv.ParseBool(valStr)
 		if err == nil && valBool {
 			return true
diff --git a/viz/charts/linkerd-viz/README.md b/viz/charts/linkerd-viz/README.md
index 4231f44d2..922701e4d 100644
--- a/viz/charts/linkerd-viz/README.md
+++ b/viz/charts/linkerd-viz/README.md
@@ -71,7 +71,7 @@ Kubernetes: `>=1.13.0-0`
 
 | Key | Type | Default | Description |
 |-----|------|---------|-------------|
-| clusterDomain | string | `"cluster.local"` | Kubernetes DNS Domain name to use   |
+| clusterDomain | string | `"cluster.local"` | Kubernetes DNS Domain name to use |
 | createdByAnnotation | string | `"linkerd.io/created-by"` |  |
 | dashboard.UID | int | `2103` |  |
 | dashboard.enforcedHostRegexp | string | `""` | Host header validation regex for the dashboard. See the [Linkerd documentation](https://linkerd.io/2/tasks/exposing-dashboard) for more information |
@@ -99,15 +99,15 @@ Kubernetes: `>=1.13.0-0`
 | grafana.resources.cpu.request | string | `nil` | Amount of CPU units that the grafana container requests |
 | grafana.resources.memory.limit | string | `nil` | Maximum amount of memory that grafana container can use |
 | grafana.resources.memory.request | string | `nil` | Amount of memory that the grafana container requests |
-| identityTrustDomain | string | `"cluster.local"` | Trust domain used for identity  |
+| identityTrustDomain | string | `"cluster.local"` | Trust domain used for identity |
 | imagePullSecrets | list | `[]` | For Private docker registries, authentication is needed.  Registry secrets are applied to the respective service accounts |
 | installNamespace | bool | `true` | Set to false when installing in a custom namespace. |
 | linkerdNamespace | string | `"linkerd"` | Namespace of the Linkerd core control-plane install |
 | linkerdVersion | string | `"linkerdVersionValue"` | control plane version. See Proxy section for proxy version |
 | namespace | string | `"linkerd-viz"` | Namespace in which the Linkerd Viz extension has to be installed |
 | nodeSelector | object | `{"beta.kubernetes.io/os":"linux"}` | NodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
-| prometheus.alertManagers | string | `nil` | Alertmanager instances the Prometheus server sends alerts to configured via the static_configs parameter. |
 | prometheus.alertRelabelConfigs | string | `nil` | Alert relabeling is applied to alerts before they are sent to the Alertmanager. |
+| prometheus.alertmanagers | string | `nil` | Alertmanager instances the Prometheus server sends alerts to configured via the static_configs parameter. |
 | prometheus.args | object | `{"config.file":"/etc/prometheus/prometheus.yml","log.level":"info","storage.tsdb.path":"/data","storage.tsdb.retention.time":"6h"}` | Command line options for Prometheus binary |
 | prometheus.enabled | bool | `true` | toggle field to enable or disable prometheus |
 | prometheus.globalConfig | object | `{"evaluation_interval":"10s","scrape_interval":"10s","scrape_timeout":"10s"}` | The global configuration specifies parameters that are valid in all other configuration contexts. |
@@ -132,7 +132,7 @@ Kubernetes: `>=1.13.0-0`
 | tap.image.name | string | `"controller"` | Docker image name for the tap instance |
 | tap.image.registry | string | `"ghcr.io/linkerd"` | Docker registry for the tap instance |
 | tap.image.tag | string | `"linkerdVersionValue"` | Docker image tag for the tap instance |
-| tap.keyPEM | string | `""` | Certificate key for Tap component. If not provided then Helm will generate one.  |
+| tap.keyPEM | string | `""` | Certificate key for Tap component. If not provided then Helm will generate one. |
 | tap.logLevel | string | `"info"` | log level of the tap component |
 | tap.proxy | string | `nil` |  |
 | tap.replicas | int | `1` |  |
@@ -140,6 +140,23 @@ Kubernetes: `>=1.13.0-0`
 | tap.resources.cpu.request | string | `nil` | Amount of CPU units that the tap container requests |
 | tap.resources.memory.limit | string | `nil` | Maximum amount of memory that tap container can use |
 | tap.resources.memory.request | string | `nil` | Amount of memory that the tap container requests |
+| tapInjector.UID | int | `2103` |  |
+| tapInjector.caBundle | string | `""` | Bundle of CA certificates for the tapInjector. If not provided then Helm will use the certificate generated  for `tapInjector.crtPEM`. If `tapInjector.externalSecret` is set to true, this value must be set, as no certificate will be generated. |
+| tapInjector.crtPEM | string | `""` | Certificate for the tapInjector. If not provided then Helm will generate one. |
+| tapInjector.externalSecret | bool | `false` | Do not create a secret resource for the tapInjector webhook. If this is set to `true`, the value `tapInjector.caBundle` must be set (see below) |
+| tapInjector.failurePolicy | string | `"Ignore"` |  |
+| tapInjector.image.name | string | `"controller"` | Docker image name for the tapInjector instance |
+| tapInjector.image.registry | string | `"ghcr.io/linkerd"` | Docker registry for the tapInjector instance |
+| tapInjector.image.tag | string | `"linkerdVersionValue"` | Docker image tag for the tapInjector instance |
+| tapInjector.keyPEM | string | `""` | Certificate key for the tapInjector. If not provided then Helm will generate one. |
+| tapInjector.namespaceSelector | string | `nil` |  |
+| tapInjector.objectSelector | string | `nil` |  |
+| tapInjector.proxy | string | `nil` |  |
+| tapInjector.replicas | int | `1` | Number of replicas of tapInjector |
+| tapInjector.resources.cpu.limit | string | `nil` | Maximum amount of CPU units that the tapInjector container can use |
+| tapInjector.resources.cpu.request | string | `nil` | Amount of CPU units that the tapInjector container requests |
+| tapInjector.resources.memory.limit | string | `nil` | Maximum amount of memory that tapInjector container can use |
+| tapInjector.resources.memory.request | string | `nil` | Amount of memory that the tapInjector container requests |
 
 ----------------------------------------------
 Autogenerated from chart metadata using [helm-docs v1.4.0](https://github.com/norwoodj/helm-docs/releases/v1.4.0)
diff --git a/viz/charts/linkerd-viz/templates/grafana.yaml b/viz/charts/linkerd-viz/templates/grafana.yaml
index b31d6df2c..a9762360e 100644
--- a/viz/charts/linkerd-viz/templates/grafana.yaml
+++ b/viz/charts/linkerd-viz/templates/grafana.yaml
@@ -130,7 +130,7 @@ spec:
         # see https://github.com/grafana/grafana/issues/20096
         - name: GODEBUG
           value: netdns=go
-        image: {{.Values.grafana.image.registry}}/{{.Values.grafana.image.name}}:{{ default (default .Values.linkerdVersion .Values.controllerImageVersion) .Values.grafana.image.tag}}
+        image: {{.Values.grafana.image.registry}}/{{.Values.grafana.image.name}}:{{.Values.grafana.image.tag}}
         imagePullPolicy: {{.Values.imagePullPolicy}}
         livenessProbe:
           httpGet:
diff --git a/viz/charts/linkerd-viz/templates/prometheus.yaml b/viz/charts/linkerd-viz/templates/prometheus.yaml
index 17a60bbf6..791705d28 100644
--- a/viz/charts/linkerd-viz/templates/prometheus.yaml
+++ b/viz/charts/linkerd-viz/templates/prometheus.yaml
@@ -154,15 +154,15 @@ data:
     {{- toYaml .Values.prometheus.scrapeConfigs | trim | nindent 4 }}
     {{- end }}
 
-    {{-  if (or .Values.prometheus.alertManagers .Values.prometheus.alertRelabelConfigs) }}
+    {{-  if (or .Values.prometheus.alertmanagers .Values.prometheus.alertRelabelConfigs) }}
     alerting:
       alert_relabel_configs:
         {{- if .Values.prometheus.alertRelabelConfigs }}
         {{- toYaml .Values.prometheus.alertRelabelConfigs | trim | nindent 6 }}
         {{- end }}
       alertmanagers:
-        {{- if .Values.prometheus.alertManagers }}
-        {{- toYaml .Values.prometheus.alertManagers | trim | nindent 6 }}
+        {{- if .Values.prometheus.alertmanagers }}
+        {{- toYaml .Values.prometheus.alertmanagers | trim | nindent 6 }}
         {{- end }}
     {{- end }}
 
diff --git a/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml b/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml
new file mode 100644
index 000000000..b4e6887f2
--- /dev/null
+++ b/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml
@@ -0,0 +1,86 @@
+###
+### Tap Injector RBAC
+###
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+rules:
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "watch"]
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+subjects:
+- kind: ServiceAccount
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+roleRef:
+  kind: ClusterRole
+  name: linkerd-tap-injector
+  apiGroup: rbac.authorization.k8s.io
+---
+kind: ServiceAccount
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+{{- include "partials.image-pull-secrets" .Values.imagePullSecrets }}
+---
+{{- $host := printf "tap-injector.%s.svc" .Values.namespace }}
+{{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}
+{{- if (not .Values.tapInjector.externalSecret) }}
+kind: Secret
+apiVersion: v1
+metadata:
+  name: tap-injector-k8s-tls
+  namespace: {{ .Values.namespace }}
+  annotations:
+    {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+type: kubernetes.io/tls
+data:
+  tls.crt: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tapInjector.crtPEM)) (empty .Values.tapInjector.crtPEM) }}
+  tls.key: {{ ternary (b64enc (trim $ca.Key)) (b64enc (trim .Values.tapInjector.keyPEM)) (empty .Values.tapInjector.keyPEM) }}
+---
+{{- end }}
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: linkerd-tap-injector-webhook-config
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+webhooks:
+- name: tap-injector.linkerd.io
+  {{- if .Values.tapInjector.namespaceSelector }}
+  namespaceSelector:
+{{ toYaml .Values.tapInjector.namespaceSelector | trim | indent 4 -}}
+  {{- end }}
+  {{- if .Values.tapInjector.objectSelector }}
+  objectSelector:
+{{ toYaml .Values.tapInjector.objectSelector | trim | indent 4 -}}
+  {{- end }}
+  clientConfig:
+    service:
+      name: tap-injector
+      namespace: {{ .Values.namespace }}
+      path: "/"
+{{- if and (.Values.tapInjector.externalSecret) (empty .Values.tapInjector.caBundle) }}
+  {{- fail "If tapInjector.externalSecret is true then you need to provide tapInjector.caBundle" }}
+{{- end }}
+    caBundle: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tapInjector.caBundle)) (empty .Values.tapInjector.caBundle) }}
+  failurePolicy: {{.Values.tapInjector.failurePolicy}}
+  reinvocationPolicy: IfNeeded
+  rules:
+  - operations: [ "CREATE" ]
+    apiGroups: [""]
+    apiVersions: ["v1"]
+    resources: ["pods"]
+  sideEffects: None
diff --git a/viz/charts/linkerd-viz/templates/tap-injector.yaml b/viz/charts/linkerd-viz/templates/tap-injector.yaml
new file mode 100644
index 000000000..bca64e376
--- /dev/null
+++ b/viz/charts/linkerd-viz/templates/tap-injector.yaml
@@ -0,0 +1,106 @@
+---
+###
+### Tap Injector
+###
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+    component: tap-injector
+  annotations:
+    {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+spec:
+  type: ClusterIP
+  selector:
+    component: tap-injector
+  ports:
+  - name: tap-injector
+    port: 443
+    targetPort: tap-injector
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  annotations:
+    {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+    app.kubernetes.io/name: tap-injector
+    app.kubernetes.io/part-of: Linkerd
+    component: tap-injector
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+spec:
+  replicas: {{.Values.tapInjector.replicas}}
+  selector:
+    matchLabels:
+      component: tap-injector
+  {{- if .Values.enablePodAntiAffinity }}
+  strategy:
+    rollingUpdate:
+      maxUnavailable: 1
+  {{- end }}
+  template:
+    metadata:
+      annotations:
+        {{- if empty .Values.cliVersion }}
+        checksum/config: {{ include (print $.Template.BasePath "/tap-injector-rbac.yaml") . | sha256sum }}
+        {{- end }}
+        {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+        {{- with .Values.tapInjector.proxy }}
+        {{- include "partials.proxy.config.annotations" .resources | nindent 8 }}
+        {{- end }}
+        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
+      labels:
+        {{.Values.extensionAnnotation}}: linkerd-viz
+        component: tap-injector
+        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}
+    spec:
+      {{- if .Values.tolerations -}}
+      {{- include "linkerd.tolerations" . | nindent 6 }}
+      {{- end -}}
+      {{- include "linkerd.node-selector" . | nindent 6 }}
+      {{- if .Values.enablePodAntiAffinity -}}
+      {{- $local := dict "component" "tap-injector" "label" "component" -}}
+      {{- include "linkerd.pod-affinity" $local | nindent 6 -}}
+      {{- end }}
+      containers:
+      - args:
+        - tap-injector
+        - -tap-service-name=linkerd-tap.{{.Values.namespace}}.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
+        image: {{.Values.tapInjector.image.registry}}/{{.Values.tapInjector.image.name}}:{{.Values.tapInjector.image.tag}}
+        imagePullPolicy: {{.Values.tapInjector.image.pullPolicy}}
+        livenessProbe:
+          httpGet:
+            path: /ping
+            port: 9995
+          initialDelaySeconds: 10
+        name: tap-injector
+        ports:
+        - containerPort: 8443
+          name: tap-injector
+        - containerPort: 9995
+          name: admin-http
+        readinessProbe:
+          failureThreshold: 7
+          httpGet:
+            path: /ready
+            port: 9995
+        {{- if .Values.tapInjector.resources -}}
+        {{- include "partials.resources" .Values.tapInjector.resources | nindent 8 }}
+        {{- end }}
+        securityContext:
+          runAsUser: {{.Values.tapInjector.UID}}
+        volumeMounts:
+        - mountPath: /var/run/linkerd/tls
+          name: tls
+          readOnly: true
+      serviceAccountName: tap-injector
+      volumes:
+      - name: tls
+        secret:
+          secretName: tap-injector-k8s-tls
diff --git a/viz/charts/linkerd-viz/templates/tap.yaml b/viz/charts/linkerd-viz/templates/tap.yaml
index 660bcf055..773cc12eb 100644
--- a/viz/charts/linkerd-viz/templates/tap.yaml
+++ b/viz/charts/linkerd-viz/templates/tap.yaml
@@ -84,7 +84,7 @@ spec:
         - -controller-namespace={{.Values.linkerdNamespace}}
         - -log-level={{.Values.tap.logLevel}}
         - -identity-trust-domain={{.Values.identityTrustDomain}}
-        image: {{.Values.tap.image.registry}}/{{.Values.tap.image.name}}:{{ default (default .Values.linkerdVersion .Values.controllerImageVersion) .Values.tap.image.tag}}
+        image: {{.Values.tap.image.registry}}/{{.Values.tap.image.name}}:{{.Values.tap.image.tag}}
         imagePullPolicy: {{.Values.tap.image.pullPolicy}}
         livenessProbe:
           httpGet:
diff --git a/viz/charts/linkerd-viz/templates/web.yaml b/viz/charts/linkerd-viz/templates/web.yaml
index b16818a64..2253decc6 100644
--- a/viz/charts/linkerd-viz/templates/web.yaml
+++ b/viz/charts/linkerd-viz/templates/web.yaml
@@ -87,7 +87,7 @@ spec:
         {{- $hostAbbrev := replace "." "\\." (printf "linkerd-web.%s.svc" .Values.namespace) }}
         - -enforced-host=^(localhost|127\.0\.0\.1|{{ $hostFull }}|{{ $hostAbbrev }}|\[::1\])(:\d+)?$
         {{- end}}
-        image: {{.Values.dashboard.image.registry}}/{{.Values.dashboard.image.name}}:{{ default (default .Values.linkerdVersion .Values.controllerImageVersion) .Values.dashboard.image.tag}}
+        image: {{.Values.dashboard.image.registry}}/{{.Values.dashboard.image.name}}:{{.Values.dashboard.image.tag}}
         imagePullPolicy: {{.Values.dashboard.image.pullPolicy}}
         livenessProbe:
           httpGet:
diff --git a/viz/charts/linkerd-viz/values.yaml b/viz/charts/linkerd-viz/values.yaml
index b9d57d305..1b504bdbd 100644
--- a/viz/charts/linkerd-viz/values.yaml
+++ b/viz/charts/linkerd-viz/values.yaml
@@ -10,10 +10,10 @@ linkerdVersion: &linkerd_version linkerdVersionValue
 # -- Default Docker Registry
 defaultRegistry: &default_registry ghcr.io/linkerd
 
-# -- Kubernetes DNS Domain name to use  
+# -- Kubernetes DNS Domain name to use
 clusterDomain: &cluster_domain cluster.local
 
-# -- Trust domain used for identity 
+# -- Trust domain used for identity
 identityTrustDomain: *cluster_domain
 
 # -- Namespace of the Linkerd core control-plane install
@@ -46,7 +46,7 @@ nodeSelector:
 imagePullSecrets: []
 # - name: my-private-docker-registry-login-secret
 
-# -|- Tolerations section, See the 
+# -|- Tolerations section, See the
 # [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)
 # for more information
 # tolerations:
@@ -78,7 +78,7 @@ tap:
   crtPEM: |
 
   # -- Certificate key for Tap component. If not provided then Helm will
-  # generate one. 
+  # generate one.
   keyPEM: |
 
   # -- Bundle of CA certificates for Tap component. If not provided then Helm
@@ -106,6 +106,60 @@ tap:
 
   UID: *uid
 
+# tapInjector configuration
+tapInjector:
+  # -- Number of replicas of tapInjector
+  replicas: 1
+  image:
+    # -- Docker registry for the tapInjector instance
+    registry: *default_registry
+    # -- Docker image name for the tapInjector instance
+    name: controller
+    # -- Docker image tag for the tapInjector instance
+    tag: *linkerd_version
+  namespaceSelector:
+    # matchExpressions:
+    # - key: runlevel
+    #   operator: NotIn
+    #   values: ["0","1"]
+  objectSelector:
+    # matchLabels:
+    #   foo: bar
+  UID: *uid
+  failurePolicy: Ignore
+  resources:
+    cpu:
+      # -- Maximum amount of CPU units that the tapInjector container can use
+      limit:
+      # -- Amount of CPU units that the tapInjector container requests
+      request:
+    memory:
+      # -- Maximum amount of memory that tapInjector container can use
+      limit:
+      # -- Amount of memory that the tapInjector container requests
+      request:
+  proxy:
+    # -- If set, overrides default proxy resources for the proxy injected
+    # into the tapInjector component
+    # resources:
+
+  # -- Do not create a secret resource for the tapInjector webhook. If this is
+  # set to `true`, the value `tapInjector.caBundle` must be set (see below)
+  externalSecret: false
+  # -- Certificate for the tapInjector. If not provided then Helm will
+  # generate one.
+  crtPEM: |
+
+  # -- Certificate key for the tapInjector. If not provided then Helm will
+  # generate one.
+  keyPEM: |
+
+  # -- Bundle of CA certificates for the tapInjector. If not provided then
+  # Helm will use the certificate generated  for `tapInjector.crtPEM`. If
+  # `tapInjector.externalSecret` is set to true, this value must be set, as no
+  # certificate will be generated.
+  caBundle: |
+
 # web dashboard configuration
 dashboard:
   # -- Number of replicas of dashboard
@@ -210,7 +264,7 @@ prometheus:
 
   # -- Alertmanager instances the Prometheus server sends alerts to configured via
   # the static_configs parameter.
-  alertManagers:
+  alertmanagers:
   # Ex:
   # - scheme: http
   #   static_configs:
diff --git a/viz/cmd/install.go b/viz/cmd/install.go
index d3c0f6f82..d508111ac 100644
--- a/viz/cmd/install.go
+++ b/viz/cmd/install.go
@@ -32,6 +32,8 @@ var (
 		"templates/grafana.yaml",
 		"templates/prometheus.yaml",
 		"templates/tap.yaml",
+		"templates/tap-injector-rbac.yaml",
+		"templates/tap-injector.yaml",
 		"templates/web.yaml",
 	}
 )


Test patch:
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index cd8112e27..7a5102828 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -40,8 +40,8 @@ jobs:
         restore-keys: |
           ${{ runner.os }}-buildx-${{ matrix.target }}-
     - name: Build docker images
-      env:
-        DOCKER_TRACE: 1
+      #env:
+      #  DOCKER_TRACE: 1
       run: |
         docker buildx create --driver docker-container --use
         bin/docker-build-${{ matrix.target }}
diff --git a/TEST.md b/TEST.md
index f93e685df..21b6fa60d 100644
--- a/TEST.md
+++ b/TEST.md
@@ -167,7 +167,7 @@ You can also test a locally-built version of the `linkerd` CLI.
 First build all of the Linkerd images by running:
 
 ```bash
-DOCKER_TRACE=1 bin/docker-build
+bin/docker-build
 ```
 
 That command also copies the corresponding `linkerd` binaries into the
diff --git a/bin/docker-test-proxy b/bin/docker-test-proxy
index b187b8ec7..baf14de95 100755
--- a/bin/docker-test-proxy
+++ b/bin/docker-test-proxy
@@ -2,21 +2,13 @@
 
 set -eu
 
-# When set, causes docker's build output to be emitted to stderr.
-export DOCKER_TRACE=${DOCKER_TRACE:-}
 export RUST_LOG=${RUST_LOG:-}
 
 bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
 rootdir=$( cd "$bindir"/.. && pwd )
 
-if [ -n "$DOCKER_TRACE" ]; then
-	output=/dev/stderr
-else
-	output=/dev/null
-fi
-
 docker build -f "$rootdir/proxy/Dockerfile" . \
     --target build \
     -t proxy-build \
-    --build-arg=PROXY_UNOPTIMIZED=1 > $output
+    --build-arg=PROXY_UNOPTIMIZED=1
 docker run --rm -it proxy-build env RUST_LOG="$RUST_LOG" cargo test "$@"
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
index 2a10b2888..69052416c 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
@@ -96,8 +96,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
index d041d5007..2d080c10b 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
@@ -96,8 +96,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -265,8 +263,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
index 4a840b0c0..80b6b500d 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
@@ -96,8 +96,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_contour.golden.yml b/cli/cmd/testdata/inject_contour.golden.yml
index d6d13c698..8366af6b4 100644
--- a/cli/cmd/testdata/inject_contour.golden.yml
+++ b/cli/cmd/testdata/inject_contour.golden.yml
@@ -125,8 +125,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml b/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
index f90fc8d21..58fdb370b 100644
--- a/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -287,8 +285,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -467,8 +463,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -647,8 +641,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
index f8384b4bd..2028b6f11 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
index 0aa30b223..9897a9fba 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
@@ -115,8 +115,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
index d6331002c..8ee40f663 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
@@ -118,8 +118,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_DISABLED
-          value: "true"
         image: ghcr.io/linkerd/proxy:override
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
index bec78d5f8..1b7e177c4 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -287,8 +285,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
index 683d57dab..85489fb2a 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
@@ -112,8 +112,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
index ec7f28235..b6d3882eb 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
index 21cd8c3f4..ca995d71f 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
@@ -108,8 +108,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
index 04557fa3c..d9be4d73b 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
index 526e5bba7..fdb07f2a6 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
@@ -108,8 +108,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
index e3a7f14ca..3d55e28fd 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
@@ -109,8 +109,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
index 931983c6f..04f4a75ff 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
@@ -109,8 +109,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_list.golden.yml b/cli/cmd/testdata/inject_emojivoto_list.golden.yml
index 4706f8f12..59e5a31f2 100644
--- a/cli/cmd/testdata/inject_emojivoto_list.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_list.golden.yml
@@ -109,8 +109,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
@@ -283,8 +281,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml b/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
index 1d8a01fd8..d01c0544c 100644
--- a/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
@@ -109,8 +109,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
@@ -283,8 +281,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
index 0b977c8a2..303d52e92 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
@@ -92,8 +92,6 @@ spec:
       value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
       value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-    - name: LINKERD2_PROXY_TAP_SVC_NAME
-      value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     image: ghcr.io/linkerd/proxy:test-inject-proxy-version
     imagePullPolicy: IfNotPresent
     livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
index 0c321a1fc..927449568 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
@@ -94,8 +94,6 @@ spec:
       value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
       value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-    - name: LINKERD2_PROXY_TAP_SVC_NAME
-      value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     image: ghcr.io/linkerd/proxy:test-inject-proxy-version
     imagePullPolicy: IfNotPresent
     livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
index 2bb69715a..4489e8082 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
@@ -96,8 +96,6 @@ spec:
       value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
       value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-    - name: LINKERD2_PROXY_TAP_SVC_NAME
-      value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     image: ghcr.io/linkerd/proxy:test-inject-proxy-version
     imagePullPolicy: IfNotPresent
     livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml b/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
index 5c2bc0a1d..bfccc5ee6 100644
--- a/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
@@ -108,8 +108,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml b/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
index f5a166cda..8a5efc45d 100644
--- a/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
+++ b/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
@@ -109,8 +109,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:testinjectversion
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -291,8 +289,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:testinjectversion
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml b/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
index d0c28e6fb..f75cadb9f 100644
--- a/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
+++ b/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
@@ -160,8 +160,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_config.golden b/cli/cmd/testdata/install_config.golden
index a39a22245..6980d77a3 100644
--- a/cli/cmd/testdata/install_config.golden
+++ b/cli/cmd/testdata/install_config.golden
@@ -352,7 +352,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -365,14 +365,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/cli/cmd/testdata/install_controlplane_tracing_output.golden b/cli/cmd/testdata/install_controlplane_tracing_output.golden
index 21d667f28..bd2d2ce18 100644
--- a/cli/cmd/testdata/install_controlplane_tracing_output.golden
+++ b/cli/cmd/testdata/install_controlplane_tracing_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -915,8 +1088,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1150,8 +1321,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1403,8 +1572,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1663,8 +1830,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1918,8 +2083,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index f3fd64ed7..4cd78e7ef 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden
index a0d9c5edd..2af449cc0 100644
--- a/cli/cmd/testdata/install_default.golden
+++ b/cli/cmd/testdata/install_default.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_default_override_dst_get_nets.golden b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
index 22538d6fa..fc5c9a56b 100644
--- a/cli/cmd/testdata/install_default_override_dst_get_nets.golden
+++ b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_grafana_existing.golden b/cli/cmd/testdata/install_grafana_existing.golden
index d899f5829..d77243da1 100644
--- a/cli/cmd/testdata/install_grafana_existing.golden
+++ b/cli/cmd/testdata/install_grafana_existing.golden
@@ -240,7 +240,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +253,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden
index c499a1e07..0042e724e 100644
--- a/cli/cmd/testdata/install_ha_output.golden
+++ b/cli/cmd/testdata/install_ha_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -995,8 +1168,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1279,8 +1450,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1581,8 +1750,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1881,8 +2048,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2202,8 +2367,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_ha_with_overrides_output.golden b/cli/cmd/testdata/install_ha_with_overrides_output.golden
index 4ef8955e3..cfb6bcb30 100644
--- a/cli/cmd/testdata/install_ha_with_overrides_output.golden
+++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -995,8 +1168,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1279,8 +1450,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1581,8 +1750,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1881,8 +2048,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2202,8 +2367,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_heartbeat_disabled_output.golden b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
index fbb3f106c..4881007b5 100644
--- a/cli/cmd/testdata/install_heartbeat_disabled_output.golden
+++ b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
@@ -190,6 +190,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -199,7 +325,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -212,14 +338,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -870,8 +1043,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1104,8 +1275,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1356,8 +1525,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1570,8 +1737,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1825,8 +1990,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output.golden b/cli/cmd/testdata/install_helm_output.golden
index d68c8f6f7..af872d573 100644
--- a/cli/cmd/testdata/install_helm_output.golden
+++ b/cli/cmd/testdata/install_helm_output.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -907,8 +1080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1132,8 +1303,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1375,8 +1544,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1628,8 +1795,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1874,8 +2039,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output_ha.golden b/cli/cmd/testdata/install_helm_output_ha.golden
index e85027d65..3a546b9cc 100644
--- a/cli/cmd/testdata/install_helm_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_output_ha.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -988,8 +1161,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1263,8 +1434,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1556,8 +1725,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1849,8 +2016,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2161,8 +2326,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output_ha_labels.golden b/cli/cmd/testdata/install_helm_output_ha_labels.golden
index 1d60a9fc7..3db8330aa 100644
--- a/cli/cmd/testdata/install_helm_output_ha_labels.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_labels.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -996,8 +1169,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1275,8 +1446,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1572,8 +1741,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1873,8 +2040,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2189,8 +2354,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
index abbd40afb..a01ddc234 100644
--- a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -988,8 +1161,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1263,8 +1434,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1556,8 +1725,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1849,8 +2016,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2161,8 +2326,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_no_init_container.golden b/cli/cmd/testdata/install_no_init_container.golden
index bb963ea98..06d0203d4 100644
--- a/cli/cmd/testdata/install_no_init_container.golden
+++ b/cli/cmd/testdata/install_no_init_container.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -911,8 +1084,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1107,8 +1278,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1321,8 +1490,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1543,8 +1710,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1760,8 +1925,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden
index 461747494..76b539986 100644
--- a/cli/cmd/testdata/install_output.golden
+++ b/cli/cmd/testdata/install_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     ControllerNamespaceLabel: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -910,8 +1083,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1146,8 +1317,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1662,8 +1829,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1919,8 +2084,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
diff --git a/cli/cmd/testdata/install_prometheus_overwrite.golden b/cli/cmd/testdata/install_prometheus_overwrite.golden
index 87657dccb..09f77b8aa 100644
--- a/cli/cmd/testdata/install_prometheus_overwrite.golden
+++ b/cli/cmd/testdata/install_prometheus_overwrite.golden
@@ -240,7 +240,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +253,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -697,7 +744,7 @@ data:
           values:
           - disabled
     prometheus:
-      alertManagers:
+      alertmanagers:
       - scheme: http
         static_configs:
         - targets:
diff --git a/cli/cmd/testdata/install_proxy_ignores.golden b/cli/cmd/testdata/install_proxy_ignores.golden
index e82ab3086..31a8bdf06 100644
--- a/cli/cmd/testdata/install_proxy_ignores.golden
+++ b/cli/cmd/testdata/install_proxy_ignores.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_tracing_overwrite.golden b/cli/cmd/testdata/install_tracing_overwrite.golden
index ecf426865..ec6549c52 100644
--- a/cli/cmd/testdata/install_tracing_overwrite.golden
+++ b/cli/cmd/testdata/install_tracing_overwrite.golden
@@ -352,7 +352,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -365,14 +365,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/cli/cmd/testdata/prom-config.yaml b/cli/cmd/testdata/prom-config.yaml
index 43f571c27..5f008a171 100644
--- a/cli/cmd/testdata/prom-config.yaml
+++ b/cli/cmd/testdata/prom-config.yaml
@@ -18,7 +18,7 @@ prometheus:
     relabel_configs:
     - action: labelmap
       regex: __meta_kubernetes_node_label_(.+)
-  alertManagers:
+  alertmanagers:
   - scheme: http
     static_configs:
     - targets:
diff --git a/test/integration/install_test.go b/test/integration/install_test.go
index 6a5b440f2..b432d0405 100644
--- a/test/integration/install_test.go
+++ b/test/integration/install_test.go
@@ -496,6 +496,7 @@ func TestInstallHelm(t *testing.T) {
 			"--set", "dashboard.image.tag=" + TestHelper.GetVersion(),
 			"--set", "grafana.image.tag=" + TestHelper.GetVersion(),
 			"--set", "tap.image.tag=" + TestHelper.GetVersion(),
+			"--set", "tapInjector.image.tag=" + TestHelper.GetVersion(),
 		}
 		// Install Viz Extension Chart
 		if stdout, stderr, err := TestHelper.HelmInstallPlain(vizChart, "l5d-viz", vizArgs...); err != nil {
@@ -605,6 +606,7 @@ func TestUpgradeHelm(t *testing.T) {
 		"--set", "dashboard.image.tag=" + TestHelper.GetVersion(),
 		"--set", "grafana.image.tag=" + TestHelper.GetVersion(),
 		"--set", "tap.image.tag=" + TestHelper.GetVersion(),
+		"--set", "tapInjector.image.tag=" + TestHelper.GetVersion(),
 		"--wait",
 	}
 	// Install Viz Extension Chart
diff --git a/test/integration/opaqueports/opaque_ports_test.go b/test/integration/opaqueports/opaque_ports_test.go
index 385be30b3..28b1afcc5 100644
--- a/test/integration/opaqueports/opaque_ports_test.go
+++ b/test/integration/opaqueports/opaque_ports_test.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"fmt"
 	"os"
-	"strings"
+	"regexp"
 	"testing"
 	"time"
 
@@ -17,9 +17,9 @@ var TestHelper *testutil.TestHelper
 var (
 	appName = "app"
 
-	// With the app's port marked as opaque, we expect to find a single open
-	// TCP connection that is not TLS'd because the port is skipped.
-	tcpMetric = "tcp_open_connections{peer=\"src\",direction=\"inbound\",tls=\"true\",client_id=\"default.linkerd-opaque-ports-test.serviceaccount.identity.linkerd.cluster.local\"}"
+	tcpMetricRE = regexp.MustCompile(
+		`^tcp_open_total\{direction="inbound",peer="src",target_addr=".*:4143",tls="true",client_id="default\.linkerd-opaque-ports-test\.serviceaccount\.identity\.linkerd\.cluster\.local"\}$`,
+	)
 )
 
 func TestMain(m *testing.M) {
@@ -78,7 +78,7 @@ func TestOpaquePorts(t *testing.T) {
 		if err != nil {
 			testutil.AnnotatedFatalf(t, "error getting metrics for pod", "error getting metrics for pod\n%s", err)
 		}
-		if !strings.Contains(metrics, tcpMetric) {
+		if tcpMetricRE.MatchString(metrics) {
 			testutil.AnnotatedFatalf(t, "failed to find expected TCP metric when port is marked as opaque", "failed to find expected TCP metric when port is marked as opaque\n%s", metrics)
 		}
 	})
diff --git a/test/integration/stat/stat_test.go b/test/integration/stat/stat_test.go
index 586059b52..836c9712d 100644
--- a/test/integration/stat/stat_test.go
+++ b/test/integration/stat/stat_test.go
@@ -80,6 +80,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 				"linkerd-prometheus": "1/1",
 				"linkerd-tap":        "1/1",
 				"linkerd-web":        "1/1",
+				"tap-injector":       "1/1",
 			},
 		},
 		{
@@ -116,7 +117,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 		{
 			args: []string{"viz", "stat", "ns", TestHelper.GetVizNamespace()},
 			expectedRows: map[string]string{
-				TestHelper.GetVizNamespace(): "4/4",
+				TestHelper.GetVizNamespace(): "5/5",
 			},
 		},
 		{
diff --git a/testutil/inject_validator.go b/testutil/inject_validator.go
index 2800904b5..bc616ca4d 100644
--- a/testutil/inject_validator.go
+++ b/testutil/inject_validator.go
@@ -77,6 +77,15 @@ func (iv *InjectValidator) validateEnvVar(container *v1.Container, envName, expe
 	return fmt.Errorf("cannot find env: %s", envName)
 }
 
+func (iv *InjectValidator) validateNoEnvVar(container *v1.Container, envName string) error {
+	for _, env := range container.Env {
+		if env.Name == envName {
+			return fmt.Errorf("env: %s, expected to not be set, actual %s", envName, env.Value)
+		}
+	}
+	return nil
+}
+
 func (iv *InjectValidator) validatePort(container *v1.Container, portName string, expectedValue int) error {
 	for _, port := range container.Ports {
 		if port.Name == portName {
@@ -129,7 +138,7 @@ func (iv *InjectValidator) validateProxyContainer(pod *v1.PodSpec) error {
 	}
 
 	if iv.DisableTap {
-		if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_TAP_DISABLED", enabled); err != nil {
+		if err := iv.validateNoEnvVar(proxyContainer, "LINKERD2_PROXY_TAP_SVC"); err != nil {
 			return err
 		}
 	}
diff --git a/viz/cmd/install_test.go b/viz/cmd/install_test.go
index 93fd4fa34..6d77c7167 100644
--- a/viz/cmd/install_test.go
+++ b/viz/cmd/install_test.go
@@ -17,6 +17,11 @@ func TestRender(t *testing.T) {
 			"crtPEM":   "test-tap-crt-pem",
 			"caBundle": "test-tap-ca-bundle",
 		},
+		"tapInjector": map[string]interface{}{
+			"keyPEM":   "test-tap-key-pem",
+			"crtPEM":   "test-tap-crt-pem",
+			"caBundle": "test-tap-ca-bundle",
+		},
 	}
 
 	testCases := []struct {
diff --git a/viz/cmd/testdata/install_default.golden b/viz/cmd/testdata/install_default.golden
index 819bf6aee..17f9f291d 100644
--- a/viz/cmd/testdata/install_default.golden
+++ b/viz/cmd/testdata/install_default.golden
@@ -841,6 +841,162 @@ spec:
         secret:
           secretName: linkerd-tap-k8s-tls
 
+###
+### Tap Injector RBAC
+###
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+rules:
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "watch"]
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+subjects:
+- kind: ServiceAccount
+  name: tap-injector
+  namespace: linkerd-viz
+roleRef:
+  kind: ClusterRole
+  name: linkerd-tap-injector
+  apiGroup: rbac.authorization.k8s.io
+---
+kind: ServiceAccount
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+---
+kind: Secret
+apiVersion: v1
+metadata:
+  name: tap-injector-k8s-tls
+  namespace: linkerd-viz
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+type: kubernetes.io/tls
+data:
+  tls.crt: dGVzdC10YXAtY3J0LXBlbQ==
+  tls.key: dGVzdC10YXAta2V5LXBlbQ==
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: linkerd-tap-injector-webhook-config
+  labels:
+    linkerd.io/extension: linkerd-viz
+webhooks:
+- name: tap-injector.linkerd.io
+  clientConfig:
+    service:
+      name: tap-injector
+      namespace: linkerd-viz
+      path: "/"
+    caBundle: dGVzdC10YXAtY2EtYnVuZGxl
+  failurePolicy: Ignore
+  reinvocationPolicy: IfNeeded
+  rules:
+  - operations: [ "CREATE" ]
+    apiGroups: [""]
+    apiVersions: ["v1"]
+    resources: ["pods"]
+  sideEffects: None
+---
+###
+### Tap Injector
+###
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: linkerd-viz
+    component: tap-injector
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+spec:
+  type: ClusterIP
+  selector:
+    component: tap-injector
+  ports:
+  - name: tap-injector
+    port: 443
+    targetPort: tap-injector
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+  labels:
+    linkerd.io/extension: linkerd-viz
+    app.kubernetes.io/name: tap-injector
+    app.kubernetes.io/part-of: Linkerd
+    component: tap-injector
+  name: tap-injector
+  namespace: linkerd-viz
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      component: tap-injector
+  template:
+    metadata:
+      annotations:
+        checksum/config: 33e988bace52d67983db9661563009d9e7f2a4877dd05add7300d2f25d2579ba
+        linkerd.io/created-by: linkerd/helm dev-undefined
+      labels:
+        linkerd.io/extension: linkerd-viz
+        component: tap-injector
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      containers:
+      - args:
+        - tap-injector
+        - -tap-service-name=linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
+        image: ghcr.io/linkerd/controller:dev-undefined
+        imagePullPolicy: 
+        livenessProbe:
+          httpGet:
+            path: /ping
+            port: 9995
+          initialDelaySeconds: 10
+        name: tap-injector
+        ports:
+        - containerPort: 8443
+          name: tap-injector
+        - containerPort: 9995
+          name: admin-http
+        readinessProbe:
+          failureThreshold: 7
+          httpGet:
+            path: /ready
+            port: 9995
+        resources:
+        securityContext:
+          runAsUser: 2103
+        volumeMounts:
+        - mountPath: /var/run/linkerd/tls
+          name: tls
+          readOnly: true
+      serviceAccountName: tap-injector
+      volumes:
+      - name: tls
+        secret:
+          secretName: tap-injector-k8s-tls
 ---
 ###
 ### Web
diff --git a/viz/cmd/testdata/install_prometheus_disabled.golden b/viz/cmd/testdata/install_prometheus_disabled.golden
index cd5522bd1..59df1ebce 100644
--- a/viz/cmd/testdata/install_prometheus_disabled.golden
+++ b/viz/cmd/testdata/install_prometheus_disabled.golden
@@ -549,6 +549,162 @@ spec:
         secret:
           secretName: linkerd-tap-k8s-tls
 
+###
+### Tap Injector RBAC
+###
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+rules:
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "watch"]
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+subjects:
+- kind: ServiceAccount
+  name: tap-injector
+  namespace: linkerd-viz
+roleRef:
+  kind: ClusterRole
+  name: linkerd-tap-injector
+  apiGroup: rbac.authorization.k8s.io
+---
+kind: ServiceAccount
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+---
+kind: Secret
+apiVersion: v1
+metadata:
+  name: tap-injector-k8s-tls
+  namespace: linkerd-viz
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+type: kubernetes.io/tls
+data:
+  tls.crt: dGVzdC10YXAtY3J0LXBlbQ==
+  tls.key: dGVzdC10YXAta2V5LXBlbQ==
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: linkerd-tap-injector-webhook-config
+  labels:
+    linkerd.io/extension: linkerd-viz
+webhooks:
+- name: tap-injector.linkerd.io
+  clientConfig:
+    service:
+      name: tap-injector
+      namespace: linkerd-viz
+      path: "/"
+    caBundle: dGVzdC10YXAtY2EtYnVuZGxl
+  failurePolicy: Ignore
+  reinvocationPolicy: IfNeeded
+  rules:
+  - operations: [ "CREATE" ]
+    apiGroups: [""]
+    apiVersions: ["v1"]
+    resources: ["pods"]
+  sideEffects: None
+---
+###
+### Tap Injector
+###
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: linkerd-viz
+    component: tap-injector
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+spec:
+  type: ClusterIP
+  selector:
+    component: tap-injector
+  ports:
+  - name: tap-injector
+    port: 443
+    targetPort: tap-injector
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+  labels:
+    linkerd.io/extension: linkerd-viz
+    app.kubernetes.io/name: tap-injector
+    app.kubernetes.io/part-of: Linkerd
+    component: tap-injector
+  name: tap-injector
+  namespace: linkerd-viz
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      component: tap-injector
+  template:
+    metadata:
+      annotations:
+        checksum/config: 33e988bace52d67983db9661563009d9e7f2a4877dd05add7300d2f25d2579ba
+        linkerd.io/created-by: linkerd/helm dev-undefined
+      labels:
+        linkerd.io/extension: linkerd-viz
+        component: tap-injector
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      containers:
+      - args:
+        - tap-injector
+        - -tap-service-name=linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
+        image: ghcr.io/linkerd/controller:dev-undefined
+        imagePullPolicy: 
+        livenessProbe:
+          httpGet:
+            path: /ping
+            port: 9995
+          initialDelaySeconds: 10
+        name: tap-injector
+        ports:
+        - containerPort: 8443
+          name: tap-injector
+        - containerPort: 9995
+          name: admin-http
+        readinessProbe:
+          failureThreshold: 7
+          httpGet:
+            path: /ready
+            port: 9995
+        resources:
+        securityContext:
+          runAsUser: 2103
+        volumeMounts:
+        - mountPath: /var/run/linkerd/tls
+          name: tls
+          readOnly: true
+      serviceAccountName: tap-injector
+      volumes:
+      - name: tls
+        secret:
+          secretName: tap-injector-k8s-tls
 ---
 ###
 ### Web