Repository: tektoncd/pipeline (language: go)

Issue:
Isolated Workspaces don't seem to be mounting properly
# Expected Behavior

[https://github.com/tektoncd/community/blob/main/teps/0029-step-workspaces.md](https://www.google.com/url?sa=D&q=https%3A%2F%2Fgithub.com%2Ftektoncd%2Fcommunity%2Fblob%2Fmain%2Fteps%2F0029-step-workspaces.md)

When we specify the workspace name in a step, only that workspace should have access to the workspace/mount. Further, if you replace a mount path, it should also update the mount path.

# Actual Behavior

Yaml passes validation, but running find or checking if a dir exists shows that a workspace is mounted to all steps even when specified to a single one.

# Steps to Reproduce the Problem

1. Start up a tekton job using the following snippet for the taskSpec:

```
        taskSpec:
          workspaces:
            - name: output
              mountPath: /job_out
           # Below two workspaces correspond to serviceAccountToken projected Volumes
            - name: gcp-creds
              mountPath: /var/run/secrets/tokens/gcp-creds
            - name: gcp-other-creds
              mountPath: /var/run/secrets/tokens/gcp-other-creds

          stepTemplate:
            computeResources:
              requests:
                cpu: 20m
                memory: 100M
              limits:
                cpu: 500m
                memory: 1G
            env:
              - name: GOOGLE_CLOUD_PROJECT
                value: project-name

          steps:
            - name: whoami-privileged-step
              image: used-image
              imagePullPolicy: Always
              workingDir: /job_out
              script: |
                #!/bin/bash
                find /var/run/secrets/tokens/
                find /var/run/secrets/tokens/ -name "*.json" -exec cat {} \;
              env:
                - name: GOOGLE_APPLICATION_CREDENTIALS
                  value: /var/run/secrets/tokens/gcp-creds/credentials.json

              # Because we specify the workspace here, the other step should not have access to this mount.
              workspaces:
                - name: gcp-creds
                  mountPath: /var/run/secrets/tokens/gcp-creds

            - name: whoami-other-step
              image: used-image
              imagePullPolicy: Always
              workingDir: /job_out
              script: |
                #!/bin/bash
                find /var/run/secrets/tokens/
                find /var/run/secrets/tokens/ -name "*.json" -exec cat {} \;
              env:
                - name: GOOGLE_APPLICATION_CREDENTIALS
                  value: /var/run/secrets/tokens/gcp-other-creds/other-credentials.json
              workspaces:
                - name: gcp-other-creds
                  mountPath: /var/run/secrets/tokens/gcp-other-creds
```

2. In logs, we see both steps have access to both sets of creds

# Additional Info

- Kubernetes version:

  **Output of `kubectl version`:**

```
Client Version: v1.29.8-dispatcher
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.29.7-gke.1274000
```

- Tekton Pipeline version:

  **Output of `tkn version` or `kubectl get pods -n tekton-pipelines -l app=tekton-pipelines-controller -o=jsonpath='{.items[0].metadata.labels.version}'`**

```
v0.60.2
```


<!-- Any other additional information -->

enable-api-fieldscs is set to alpha

As an aside, the docs indicate that this is in beta, but it seems like as of [https://github.com/tektoncd/pipeline/commit/ecb11fc82a647fe706112998c463bb23e7de0028](https://www.google.com/url?sa=D&q=https%3A%2F%2Fgithub.com%2Ftektoncd%2Fpipeline%2Fcommit%2Fecb11fc82a647fe706112998c463bb23e7de0028), it has no flag protection?




Discussion / hints:
/assign
The issue is the use of `stepTemplate` 😓 . The following showcase the issue.

```yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: secret1
type: Opaque
stringData:
  username: user
stringData:
  foo: bar
  bar: baz
---
apiVersion: v1
kind: Secret
metadata:
  name: secret2
type: Opaque
stringData:
  username: user
data:
  message: aGVsbG8gc2VjcmV0
---
kind: Task
apiVersion: tekton.dev/v1
metadata:
  name: workspace-isolation
spec:
  workspaces:
    - name: output
      mountPath: /job_out
   # Below two workspaces correspond to serviceAccountToken projected Volumes
    - name: gcp-creds
      mountPath: /var/run/secrets/tokens/gcp-creds
    - name: gcp-other-creds
      mountPath: /var/run/secrets/tokens/gcp-other-creds

  steps:
    - name: whoami-privileged-step
      image: ubuntu
      workingDir: /job_out
      script: |
        #!/bin/bash
        find /var/run/secrets/tokens/
        find /var/run/secrets/tokens/ -name "*.json" -exec cat {} \;
      env:
        - name: GOOGLE_APPLICATION_CREDENTIALS
          value: /var/run/secrets/tokens/gcp-creds/credentials.json

      # Because we specify the workspace here, the other step should not have access to this mount.
      workspaces:
        - name: gcp-creds
          mountPath: /var/run/secrets/tokens/gcp-creds

    - name: whoami-other-step
      image: ubuntu
      workingDir: /job_out
      script: |
        #!/bin/bash
        find /var/run/secrets/tokens/
        find /var/run/secrets/tokens/ -name "*.json" -exec cat {} \;
      env:
        - name: GOOGLE_APPLICATION_CREDENTIALS
          value: /var/run/secrets/tokens/gcp-other-creds/other-credentials.json
      workspaces:
        - name: gcp-other-creds
          mountPath: /var/run/secrets/tokens/gcp-other-creds

---
kind: Task
apiVersion: tekton.dev/v1
metadata:
  name: workspace-isolation-steptemplate
spec:
  workspaces:
    - name: output
      mountPath: /job_out
   # Below two workspaces correspond to serviceAccountToken projected Volumes
    - name: gcp-creds
      mountPath: /var/run/secrets/tokens/gcp-creds
    - name: gcp-other-creds
      mountPath: /var/run/secrets/tokens/gcp-other-creds

  stepTemplate:
    env:
      - name: GOOGLE_CLOUD_PROJECT
        value: project-name
  steps:
    - name: whoami-privileged-step
      image: ubuntu
      workingDir: /job_out
      script: |
        #!/bin/bash
        find /var/run/secrets/tokens/
        find /var/run/secrets/tokens/ -name "*.json" -exec cat {} \;
      env:
        - name: GOOGLE_APPLICATION_CREDENTIALS
          value: /var/run/secrets/tokens/gcp-creds/credentials.json

      # Because we specify the workspace here, the other step should not have access to this mount.
      workspaces:
        - name: gcp-creds
          mountPath: /var/run/secrets/tokens/gcp-creds

    - name: whoami-other-step
      image: ubuntu
      workingDir: /job_out
      script: |
        #!/bin/bash
        find /var/run/secrets/tokens/
        find /var/run/secrets/tokens/ -name "*.json" -exec cat {} \;
      env:
        - name: GOOGLE_APPLICATION_CREDENTIALS
          value: /var/run/secrets/tokens/gcp-other-creds/other-credentials.json
      workspaces:
        - name: gcp-other-creds
          mountPath: /var/run/secrets/tokens/gcp-other-creds
---
kind: TaskRun
apiVersion: tekton.dev/v1
metadata:
  name: foo-isolation
spec:
  workspaces:
  - name: output
    emptyDir: {}
  - name: gcp-creds
    secret:
      secretName: secret1
  - name: gcp-other-creds
    secret:
      secretName: secret2
  taskRef:
    name: workspace-isolation
---
kind: TaskRun
apiVersion: tekton.dev/v1
metadata:
  name: foo-isolation-steptemplate
spec:
  workspaces:
  - name: output
    emptyDir: {}
  - name: gcp-creds
    secret:
      secretName: secret1
  - name: gcp-other-creds
    secret:
      secretName: secret2
  taskRef:
    name: workspace-isolation-steptemplate
```

And the output
```bash
$ tkn tr logs -f
? Select taskrun: foo-isolation started 2 seconds ago
[whoami-privileged-step] /var/run/secrets/tokens/
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.889050390
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.889050390/foo
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.889050390/bar
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..data
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/foo
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/bar

[whoami-other-step] /var/run/secrets/tokens/
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1674205133
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1674205133/message
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1674205133/username
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..data
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/message
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/username

$ tkn tr logs -f
? Select taskrun: foo-isolation-steptemplate started 19 seconds ago
[whoami-privileged-step] /var/run/secrets/tokens/
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.1011202968
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.1011202968/bar
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.1011202968/foo
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/..data
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/foo
[whoami-privileged-step] /var/run/secrets/tokens/gcp-creds/bar
[whoami-privileged-step] /var/run/secrets/tokens/gcp-other-creds
[whoami-privileged-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1418043587
[whoami-privileged-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1418043587/message
[whoami-privileged-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1418043587/username
[whoami-privileged-step] /var/run/secrets/tokens/gcp-other-creds/..data
[whoami-privileged-step] /var/run/secrets/tokens/gcp-other-creds/message
[whoami-privileged-step] /var/run/secrets/tokens/gcp-other-creds/username

[whoami-other-step] /var/run/secrets/tokens/
[whoami-other-step] /var/run/secrets/tokens/gcp-creds
[whoami-other-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.1011202968
[whoami-other-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.1011202968/bar
[whoami-other-step] /var/run/secrets/tokens/gcp-creds/..2024_09_17_10_13_28.1011202968/foo
[whoami-other-step] /var/run/secrets/tokens/gcp-creds/..data
[whoami-other-step] /var/run/secrets/tokens/gcp-creds/foo
[whoami-other-step] /var/run/secrets/tokens/gcp-creds/bar
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1418043587
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1418043587/message
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..2024_09_17_10_13_28.1418043587/username
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/..data
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/message
[whoami-other-step] /var/run/secrets/tokens/gcp-other-creds/username

```

We'll need a non-regression test  for this, I'll add it to my fix.

Fix patch:
diff --git a/pkg/apis/pipeline/v1/merge.go b/pkg/apis/pipeline/v1/merge.go
index df413a5456a..b916d8caeb5 100644
--- a/pkg/apis/pipeline/v1/merge.go
+++ b/pkg/apis/pipeline/v1/merge.go
@@ -65,7 +65,18 @@ func MergeStepsWithStepTemplate(template *StepTemplate, steps []Step) ([]Step, e
 		amendConflictingContainerFields(&merged, s)
 
 		// Pass through original step Script, for later conversion.
-		newStep := Step{Script: s.Script, OnError: s.OnError, Timeout: s.Timeout, StdoutConfig: s.StdoutConfig, StderrConfig: s.StderrConfig, Results: s.Results, Params: s.Params, Ref: s.Ref, When: s.When}
+		newStep := Step{
+			Script:       s.Script,
+			OnError:      s.OnError,
+			Timeout:      s.Timeout,
+			StdoutConfig: s.StdoutConfig,
+			StderrConfig: s.StderrConfig,
+			Results:      s.Results,
+			Params:       s.Params,
+			Ref:          s.Ref,
+			When:         s.When,
+			Workspaces:   s.Workspaces,
+		}
 		newStep.SetContainerFields(merged)
 		steps[i] = newStep
 	}


Test patch:
diff --git a/pkg/apis/pipeline/v1/merge_test.go b/pkg/apis/pipeline/v1/merge_test.go
index ab3d598742a..07c24e0ab5a 100644
--- a/pkg/apis/pipeline/v1/merge_test.go
+++ b/pkg/apis/pipeline/v1/merge_test.go
@@ -269,6 +269,48 @@ func TestMergeStepsWithStepTemplate(t *testing.T) {
 			Image: "some-image",
 			When:  v1.StepWhenExpressions{{Input: "foo", Operator: selection.In, Values: []string{"foo", "bar"}}},
 		}},
+	}, {
+		name: "isolated workspaces",
+		template: &v1.StepTemplate{
+			Env: []corev1.EnvVar{{
+				Name:  "KEEP_THIS",
+				Value: "A_VALUE",
+			}},
+		},
+		steps: []v1.Step{{
+			Image: "some-image",
+			Workspaces: []v1.WorkspaceUsage{{
+				Name:      "foo",
+				MountPath: "/foo/bar",
+			}},
+		}, {
+			Image: "some-image",
+			Workspaces: []v1.WorkspaceUsage{{
+				Name:      "bar",
+				MountPath: "/bar/baz",
+			}},
+		}},
+		expected: []v1.Step{{
+			Image: "some-image",
+			Env: []corev1.EnvVar{{
+				Name:  "KEEP_THIS",
+				Value: "A_VALUE",
+			}},
+			Workspaces: []v1.WorkspaceUsage{{
+				Name:      "foo",
+				MountPath: "/foo/bar",
+			}},
+		}, {
+			Image: "some-image",
+			Env: []corev1.EnvVar{{
+				Name:  "KEEP_THIS",
+				Value: "A_VALUE",
+			}},
+			Workspaces: []v1.WorkspaceUsage{{
+				Name:      "bar",
+				MountPath: "/bar/baz",
+			}},
+		}},
 	}} {
 		t.Run(tc.name, func(t *testing.T) {
 			result, err := v1.MergeStepsWithStepTemplate(tc.template, tc.steps)