From 520456bb0c9eb53378ea60be9eb988b6ee2310d7 Mon Sep 17 00:00:00 2001 From: Lee Bernick Date: Mon, 10 Apr 2023 10:41:10 -0400 Subject: [PATCH] Add more secure SecurityContext to injected pod containers This commit adds a SecurityContext to containers injected into TaskRun pods to allow them to run in namespaces with "restricted" pod security admission policies. This includes both init containers and the sidecar container which extracts results. See https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted. It assumes that a TaskRun is meant to run on Windows if and only if it has a podTemplate with a nodeSelector containing "kubernetes.io/os: windows". (See https://kubernetes.io/docs/concepts/windows/user-guide/.) This functionality is guarded behind a feature flag that defaults to false, as it may not work on all Kubernetes implementations. --- config/config-feature-flags.yaml | 4 + docs/additional-configs.md | 8 + pkg/apis/config/feature_flags.go | 7 + pkg/apis/config/feature_flags_test.go | 11 +- .../testdata/feature-flags-all-flags-set.yaml | 1 + pkg/pod/pod.go | 67 ++++- pkg/pod/pod_test.go | 278 +++++++++++++++--- pkg/pod/script.go | 7 +- pkg/pod/script_test.go | 39 +-- pkg/pod/workingdir_init.go | 12 +- pkg/pod/workingdir_init_test.go | 91 +++++- 11 files changed, 451 insertions(+), 74 deletions(-) diff --git a/config/config-feature-flags.yaml b/config/config-feature-flags.yaml index 2a04a0fd073..636563fb58b 100644 --- a/config/config-feature-flags.yaml +++ b/config/config-feature-flags.yaml @@ -91,3 +91,7 @@ data: # If set to "none", then Tekton will not have non-falsifiable provenance. # This is an experimental feature and thus should still be considered an alpha feature. enforce-nonfalsifiablity: "none" + # Setting this flag to "true" will limit privileges for containers injected by Tekton into TaskRuns. + # This allows TaskRuns to run in namespaces with "restricted" pod security standards. + # Not all Kubernetes implementations support this option. + set-security-context: "false" diff --git a/docs/additional-configs.md b/docs/additional-configs.md index 9a5a0ea2209..97cc3f0af6a 100644 --- a/docs/additional-configs.md +++ b/docs/additional-configs.md @@ -349,6 +349,14 @@ to better fit specific usecases. Out-of-the-box, Tekton Pipelines Controller is configured for relatively small-scale deployments but there have several options for configuring Pipelines' performance are available. See the [Performance Configuration](tekton-controller-performance-configuration.md) document which describes how to change the default ThreadsPerController, QPS and Burst settings to meet your requirements. +## Running TaskRuns and PipelineRuns with restricted pod security standards + +To allow TaskRuns and PipelineRuns to run in namespaces with [restricted pod security standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/), +set the "set-security-context" feature flag to "true" in the [feature-flags configMap](#customizing-the-pipelines-controller-behavior). This configuration option applies a [SecurityContext](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) +to any containers injected into TaskRuns by the Pipelines controller. This SecurityContext may not be supported in all Kubernetes implementations (for example, OpenShift). + +**Note**: running TaskRuns and PipelineRuns in the "tekton-pipelines" namespace is discouraged. + ## Platform Support The Tekton project provides support for running on x86 Linux Kubernetes nodes. diff --git a/pkg/apis/config/feature_flags.go b/pkg/apis/config/feature_flags.go index 3b5350db346..3f781f57183 100644 --- a/pkg/apis/config/feature_flags.go +++ b/pkg/apis/config/feature_flags.go @@ -76,6 +76,8 @@ const ( DefaultResultExtractionMethod = ResultExtractionMethodTerminationMessage // DefaultMaxResultSize is the default value in bytes for the size of a result DefaultMaxResultSize = 4096 + // DefaultSetSecurityContext is the default value for "set-security-context" + DefaultSetSecurityContext = false disableAffinityAssistantKey = "disable-affinity-assistant" disableCredsInitKey = "disable-creds-init" @@ -90,6 +92,7 @@ const ( enableProvenanceInStatus = "enable-provenance-in-status" resultExtractionMethod = "results-from" maxResultSize = "max-result-size" + setSecurityContextKey = "set-security-context" ) // DefaultFeatureFlags holds all the default configurations for the feature flags configmap. @@ -119,6 +122,7 @@ type FeatureFlags struct { EnableProvenanceInStatus bool ResultExtractionMethod string MaxResultSize int + SetSecurityContext bool } // GetFeatureFlagsConfigName returns the name of the configmap containing all @@ -182,6 +186,9 @@ func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) { if err := setEnforceNonFalsifiability(cfgMap, tc.EnableAPIFields, &tc.EnforceNonfalsifiability); err != nil { return nil, err } + if err := setFeature(setSecurityContextKey, DefaultSetSecurityContext, &tc.SetSecurityContext); err != nil { + return nil, err + } // Given that they are alpha features, Tekton Bundles and Custom Tasks should be switched on if // enable-api-fields is "alpha". If enable-api-fields is not "alpha" then fall back to the value of diff --git a/pkg/apis/config/feature_flags_test.go b/pkg/apis/config/feature_flags_test.go index 10cfa1150ea..2dded6be81d 100644 --- a/pkg/apis/config/feature_flags_test.go +++ b/pkg/apis/config/feature_flags_test.go @@ -51,6 +51,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus, ResultExtractionMethod: config.DefaultResultExtractionMethod, MaxResultSize: config.DefaultMaxResultSize, + SetSecurityContext: config.DefaultSetSecurityContext, }, fileName: config.GetFeatureFlagsConfigName(), }, @@ -67,8 +68,8 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { VerificationNoMatchPolicy: config.FailNoMatchPolicy, EnableProvenanceInStatus: false, ResultExtractionMethod: "termination-message", - - MaxResultSize: 4096, + MaxResultSize: 4096, + SetSecurityContext: true, }, fileName: "feature-flags-all-flags-set", }, @@ -89,6 +90,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus, ResultExtractionMethod: config.DefaultResultExtractionMethod, MaxResultSize: config.DefaultMaxResultSize, + SetSecurityContext: config.DefaultSetSecurityContext, }, fileName: "feature-flags-enable-api-fields-overrides-bundles-and-custom-tasks", }, @@ -107,6 +109,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus, ResultExtractionMethod: config.DefaultResultExtractionMethod, MaxResultSize: config.DefaultMaxResultSize, + SetSecurityContext: config.DefaultSetSecurityContext, }, fileName: "feature-flags-bundles-and-custom-tasks", }, @@ -125,6 +128,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus, ResultExtractionMethod: config.DefaultResultExtractionMethod, MaxResultSize: config.DefaultMaxResultSize, + SetSecurityContext: config.DefaultSetSecurityContext, }, fileName: "feature-flags-beta-api-fields", }, @@ -139,6 +143,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus, ResultExtractionMethod: config.DefaultResultExtractionMethod, MaxResultSize: config.DefaultMaxResultSize, + SetSecurityContext: config.DefaultSetSecurityContext, }, fileName: "feature-flags-enforce-nonfalsifiability-spire", }, @@ -151,6 +156,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus, ResultExtractionMethod: config.ResultExtractionMethodSidecarLogs, MaxResultSize: 8192, + SetSecurityContext: config.DefaultSetSecurityContext, }, fileName: "feature-flags-results-via-sidecar-logs", }, @@ -181,6 +187,7 @@ func TestNewFeatureFlagsFromEmptyConfigMap(t *testing.T) { EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus, ResultExtractionMethod: config.DefaultResultExtractionMethod, MaxResultSize: config.DefaultMaxResultSize, + SetSecurityContext: config.DefaultSetSecurityContext, } verifyConfigFileWithExpectedFeatureFlagsConfig(t, FeatureFlagsConfigEmptyName, expectedConfig) } diff --git a/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml b/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml index 696ca3c086c..f3395baf4f7 100644 --- a/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml +++ b/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml @@ -29,3 +29,4 @@ data: enforce-nonfalsifiability: "spire" trusted-resources-verification-no-match-policy: "fail" enable-provenance-in-status: "false" + set-security-context: "true" diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go index e740d59796c..91af5c6cb14 100644 --- a/pkg/pod/pod.go +++ b/pkg/pod/pod.go @@ -57,6 +57,9 @@ const ( // SpiffeCsiDriver is the CSI storage plugin needed for injection of SPIFFE workload api. SpiffeCsiDriver = "csi.spiffe.io" + + // osSelectorLabel is the label Kubernetes uses for OS-specific workloads (https://kubernetes.io/docs/reference/labels-annotations-taints/#kubernetes-io-os) + osSelectorLabel = "kubernetes.io/os" ) // These are effectively const, but Go doesn't have such an annotation. @@ -99,6 +102,27 @@ var ( // MaxActiveDeadlineSeconds is a maximum permitted value to be used for a task with no timeout MaxActiveDeadlineSeconds = int64(math.MaxInt32) + + // Used in security context of pod init containers + allowPrivilegeEscalation = false + runAsNonRoot = true + + // The following security contexts allow init containers to run in namespaces + // with "restricted" pod security admission + // See https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted + linuxSecurityContext = &corev1.SecurityContext{ + AllowPrivilegeEscalation: &allowPrivilegeEscalation, + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + RunAsNonRoot: &runAsNonRoot, + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + } + windowsSecurityContext = &corev1.SecurityContext{ + RunAsNonRoot: &runAsNonRoot, + } ) // Builder exposes options to configure Pod construction from TaskSpecs/Runs. @@ -127,6 +151,7 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec defaultForbiddenEnv := config.FromContextOrDefaults(ctx).Defaults.DefaultForbiddenEnv alphaAPIEnabled := featureFlags.EnableAPIFields == config.AlphaAPIFields sidecarLogsResultsEnabled := config.FromContextOrDefaults(ctx).FeatureFlags.ResultExtractionMethod == config.ResultExtractionMethodSidecarLogs + setSecurityContext := config.FromContextOrDefaults(ctx).FeatureFlags.SetSecurityContext // Add our implicit volumes first, so they can be overridden by the user if they prefer. volumes = append(volumes, implicitVolumes...) @@ -161,9 +186,10 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec if alphaAPIEnabled && taskRun.Spec.ComputeResources != nil { tasklevel.ApplyTaskLevelComputeResources(steps, taskRun.Spec.ComputeResources) } + windows := usesWindows(taskRun) if sidecarLogsResultsEnabled && taskSpec.Results != nil { // create a results sidecar - resultsSidecar := createResultsSidecar(taskSpec, b.Images.SidecarLogResultsImage) + resultsSidecar := createResultsSidecar(taskSpec, b.Images.SidecarLogResultsImage, setSecurityContext, windows) taskSpec.Sidecars = append(taskSpec.Sidecars, resultsSidecar) commonExtraEntrypointArgs = append(commonExtraEntrypointArgs, "-result_from", config.ResultExtractionMethodSidecarLogs) } @@ -173,15 +199,15 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec } initContainers = []corev1.Container{ - entrypointInitContainer(b.Images.EntrypointImage, steps), + entrypointInitContainer(b.Images.EntrypointImage, steps, setSecurityContext, windows), } // Convert any steps with Script to command+args. // If any are found, append an init container to initialize scripts. if alphaAPIEnabled { - scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, b.Images.ShellImageWin, steps, sidecars, taskRun.Spec.Debug) + scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, b.Images.ShellImageWin, steps, sidecars, taskRun.Spec.Debug, setSecurityContext) } else { - scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, "", steps, sidecars, nil) + scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, "", steps, sidecars, nil, setSecurityContext) } if scriptsInit != nil { @@ -192,7 +218,7 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec volumes = append(volumes, debugScriptsVolume, debugInfoVolume) } // Initialize any workingDirs under /workspace. - if workingDirInit := workingDirInit(b.Images.WorkingDirInitImage, stepContainers); workingDirInit != nil { + if workingDirInit := workingDirInit(b.Images.WorkingDirInitImage, stepContainers, setSecurityContext, windows); workingDirInit != nil { initContainers = append(initContainers, *workingDirInit) } @@ -497,9 +523,9 @@ func runVolume(i int) corev1.Volume { } } -// entrypointInitContainer generates a few init containers based of a set of command (in images) and volumes to run +// entrypointInitContainer generates a few init containers based of a set of command (in images), volumes to run, and whether the pod will run on a windows node // This should effectively merge multiple command and volumes together. -func entrypointInitContainer(image string, steps []v1beta1.Step) corev1.Container { +func entrypointInitContainer(image string, steps []v1beta1.Step, setSecurityContext, windows bool) corev1.Container { // Invoke the entrypoint binary in "cp mode" to copy itself // into the correct location for later steps and initialize steps folder command := []string{"/ko-app/entrypoint", "init", "/ko-app/entrypoint", entrypointBinary} @@ -507,6 +533,10 @@ func entrypointInitContainer(image string, steps []v1beta1.Step) corev1.Containe command = append(command, StepName(s.Name, i)) } volumeMounts := []corev1.VolumeMount{binMount, internalStepsMount} + securityContext := linuxSecurityContext + if windows { + securityContext = windowsSecurityContext + } // Rewrite steps with entrypoint binary. Append the entrypoint init // container to place the entrypoint binary. Also add timeout flags @@ -521,20 +551,39 @@ func entrypointInitContainer(image string, steps []v1beta1.Step) corev1.Containe Command: command, VolumeMounts: volumeMounts, } + if setSecurityContext { + prepareInitContainer.SecurityContext = securityContext + } return prepareInitContainer } // createResultsSidecar creates a sidecar that will run the sidecarlogresults binary. -func createResultsSidecar(taskSpec v1beta1.TaskSpec, image string) v1beta1.Sidecar { +func createResultsSidecar(taskSpec v1beta1.TaskSpec, image string, setSecurityContext, windows bool) v1beta1.Sidecar { names := make([]string, 0, len(taskSpec.Results)) for _, r := range taskSpec.Results { names = append(names, r.Name) } + securityContext := linuxSecurityContext + if windows { + securityContext = windowsSecurityContext + } resultsStr := strings.Join(names, ",") command := []string{"/ko-app/sidecarlogresults", "-results-dir", pipeline.DefaultResultPath, "-result-names", resultsStr} - return v1beta1.Sidecar{ + sidecar := v1beta1.Sidecar{ Name: pipeline.ReservedResultsSidecarName, Image: image, Command: command, } + if setSecurityContext { + sidecar.SecurityContext = securityContext + } + return sidecar +} + +func usesWindows(tr *v1beta1.TaskRun) bool { + if tr.Spec.PodTemplate == nil || tr.Spec.PodTemplate.NodeSelector == nil { + return false + } + osSelector := tr.Spec.PodTemplate.NodeSelector[osSelectorLabel] + return osSelector == "windows" } diff --git a/pkg/pod/pod_test.go b/pkg/pod/pod_test.go index a0acc9eb9b8..ad94f752026 100644 --- a/pkg/pod/pod_test.go +++ b/pkg/pod/pod_test.go @@ -109,7 +109,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -156,7 +156,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -201,7 +201,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -247,7 +247,7 @@ func TestPodBuild(t *testing.T) { want: &corev1.PodSpec{ ServiceAccountName: "service-account", RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -314,7 +314,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -371,7 +371,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "a-very-very-long-character-step-name-to-trigger-max-len----and-invalid-characters"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "a-very-very-long-character-step-name-to-trigger-max-len----and-invalid-characters"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-a-very-very-long-character-step-name-to-trigger-max-len", // step name trimmed. Image: "image", @@ -413,7 +413,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "ends-with-invalid-%%__$$"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "ends-with-invalid-%%__$$"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-ends-with-invalid", // invalid suffix removed. Image: "image", @@ -457,7 +457,7 @@ func TestPodBuild(t *testing.T) { want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}), + entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), { Name: "working-dir-initializer", Image: images.WorkingDirInitImage, @@ -514,7 +514,7 @@ func TestPodBuild(t *testing.T) { wantAnnotations: map[string]string{}, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-primary-name", Image: "primary-image", @@ -569,7 +569,7 @@ func TestPodBuild(t *testing.T) { want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}}), + entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */), { Name: "place-scripts", Image: "busybox", @@ -638,7 +638,7 @@ _EOF_ wantAnnotations: map[string]string{}, // no ready annotations on pod create since sidecars are present want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-primary-name", Image: "primary-image", @@ -700,7 +700,7 @@ _EOF_ InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{ {Name: "unnamed-0"}, {Name: "unnamed-1"}, - })}, + }, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-unnamed-0", Image: "image", @@ -798,7 +798,7 @@ _EOF_ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{ {Name: "step1"}, - })}, + }, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-step1", Image: "image", @@ -867,7 +867,7 @@ _EOF_ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{ {Name: "step1"}, - })}, + }, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-step1", Image: "image", @@ -937,7 +937,7 @@ _EOF_ wantAnnotations: map[string]string{}, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-primary-name", Image: "primary-image", @@ -1007,7 +1007,7 @@ print("Hello from Python")`, {Name: "one"}, {Name: "two"}, {Name: "regular-step"}, - }), + }, false /* setSecurityContext */, false /* windows */), { Name: "place-scripts", Image: images.ShellImage, @@ -1129,7 +1129,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "one"}}), + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "one"}}, false /* setSecurityContext */, false /* windows */), { Name: "place-scripts", Image: images.ShellImage, @@ -1192,7 +1192,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "schedule-me"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "schedule-me"}}, false /* setSecurityContext */, false /* windows */)}, SchedulerName: "there-scheduler", Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -1243,7 +1243,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "image-pull"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "image-pull"}}, false /* setSecurityContext */, false /* windows */)}, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}}, @@ -1293,7 +1293,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "host-aliases"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "host-aliases"}}, false /* setSecurityContext */, false /* windows */)}, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}}, @@ -1342,7 +1342,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "use-my-hostNetwork"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "use-my-hostNetwork"}}, false /* setSecurityContext */, false /* windows */)}, HostNetwork: true, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -1386,7 +1386,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1434,7 +1434,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1481,7 +1481,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1526,7 +1526,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1579,7 +1579,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1630,7 +1630,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1680,7 +1680,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1746,7 +1746,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1791,7 +1791,7 @@ _EOF_ wantPodName: "task-run-0123456789-01234560d38957287bb0283c59440df14069f59-pod", want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1851,7 +1851,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "use-topologySpreadConstraints"}})}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "use-topologySpreadConstraints"}}, false /* setSecurityContext */, false /* windows */)}, TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ { MaxSkew: 1, @@ -1911,7 +1911,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}), + entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -1964,6 +1964,120 @@ _EOF_ }), ActiveDeadlineSeconds: &defaultActiveDeadlineSeconds, }, + }, { + desc: "sidecar logs enabled with security context", + featureFlags: map[string]string{"results-from": "sidecar-logs", "set-security-context": "true"}, + ts: v1beta1.TaskSpec{ + Results: []v1beta1.TaskResult{{ + Name: "foo", + Type: v1beta1.ResultsTypeString, + }}, + Steps: []v1beta1.Step{{ + Name: "name", + Image: "image", + Command: []string{"cmd"}, // avoid entrypoint lookup. + }}, + }, + want: &corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + InitContainers: []corev1.Container{ + entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, true /* setSecurityContext */, false /* windows */), + }, + Containers: []corev1.Container{{ + Name: "step-name", + Image: "image", + Command: []string{"/tekton/bin/entrypoint"}, + Args: []string{ + "-wait_file", + "/tekton/downward/ready", + "-wait_file_content", + "-post_file", + "/tekton/run/0/out", + "-termination_path", + "/tekton/termination", + "-step_metadata_dir", + "/tekton/run/0/status", + "-result_from", + "sidecar-logs", + "-results", + "foo", + "-entrypoint", + "cmd", + "--", + }, + VolumeMounts: append([]corev1.VolumeMount{binROMount, runMount(0, false), downwardMount, { + Name: "tekton-creds-init-home-0", + MountPath: "/tekton/creds", + }}, implicitVolumeMounts...), + TerminationMessagePath: "/tekton/termination", + }, { + Name: pipeline.ReservedResultsSidecarContainerName, + Image: "", + Command: []string{ + "/ko-app/sidecarlogresults", + "-results-dir", + "/tekton/results", + "-result-names", + "foo", + }, + Resources: corev1.ResourceRequirements{ + Requests: nil, + }, + VolumeMounts: append([]v1.VolumeMount{ + {Name: "tekton-internal-bin", ReadOnly: true, MountPath: "/tekton/bin"}, + {Name: "tekton-internal-run-0", ReadOnly: true, MountPath: "/tekton/run/0"}, + }, implicitVolumeMounts...), + SecurityContext: linuxSecurityContext, + }}, + Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ + Name: "tekton-creds-init-home-0", + VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}}, + }), + ActiveDeadlineSeconds: &defaultActiveDeadlineSeconds, + }, + }, { + desc: "simple with security context", + ts: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Name: "name", + Image: "image", + Command: []string{"cmd"}, // avoid entrypoint lookup. + }}, + }, + featureFlags: map[string]string{"set-security-context": "true"}, + want: &corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, true /* setSecurityContext */, false /* windows */)}, + Containers: []corev1.Container{{ + Name: "step-name", + Image: "image", + Command: []string{"/tekton/bin/entrypoint"}, + Args: []string{ + "-wait_file", + "/tekton/downward/ready", + "-wait_file_content", + "-post_file", + "/tekton/run/0/out", + "-termination_path", + "/tekton/termination", + "-step_metadata_dir", + "/tekton/run/0/status", + "-entrypoint", + "cmd", + "--", + }, + VolumeMounts: append([]corev1.VolumeMount{downwardMount, { + Name: "tekton-creds-init-home-0", + MountPath: "/tekton/creds", + }, runMount(0, false), binROMount}, implicitVolumeMounts...), + TerminationMessagePath: "/tekton/termination", + }}, + Volumes: append(implicitVolumes, binVolume, downwardVolume, corev1.Volume{ + Name: "tekton-creds-init-home-0", + VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}}, + }, runVolume(0)), + ActiveDeadlineSeconds: &defaultActiveDeadlineSeconds, + }, }} { t.Run(c.desc, func(t *testing.T) { names.TestingSeed() @@ -2146,7 +2260,7 @@ debug-fail-continue-heredoc-randomly-generated-mz4c7 }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}), placeScriptsContainer}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), placeScriptsContainer}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -2457,7 +2571,7 @@ func TestPodBuild_TaskLevelResourceRequirements(t *testing.T) { } func TestPodBuildwithSpireEnabled(t *testing.T) { - initContainers := []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})} + initContainers := []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)} readonly := true for i := range initContainers { c := &initContainers[i] @@ -2759,10 +2873,12 @@ func TestIsPodReadyImmediately(t *testing.T) { func TestPrepareInitContainers(t *testing.T) { tcs := []struct { - name string - steps []v1beta1.Step - want corev1.Container - featureFlags map[string]string + name string + steps []v1beta1.Step + windows bool + setSecurityContext bool + want corev1.Container + featureFlags map[string]string }{{ name: "nothing-special", steps: []v1beta1.Step{{ @@ -2789,13 +2905,97 @@ func TestPrepareInitContainers(t *testing.T) { Command: []string{"/ko-app/entrypoint", "init", "/ko-app/entrypoint", entrypointBinary, "step-foo", "step-bar"}, VolumeMounts: []corev1.VolumeMount{binMount, internalStepsMount}, }, + }, { + name: "nothing-special-two-steps-security-context", + steps: []v1beta1.Step{{ + Name: "foo", + }, { + Name: "bar", + }}, + setSecurityContext: true, + want: corev1.Container{ + Name: "prepare", + Image: images.EntrypointImage, + WorkingDir: "/", + Command: []string{"/ko-app/entrypoint", "init", "/ko-app/entrypoint", entrypointBinary, "step-foo", "step-bar"}, + VolumeMounts: []corev1.VolumeMount{binMount, internalStepsMount}, + SecurityContext: linuxSecurityContext, + }, + }, { + name: "nothing-special-two-steps-windows", + steps: []v1beta1.Step{{ + Name: "foo", + }, { + Name: "bar", + }}, + windows: true, + want: corev1.Container{ + Name: "prepare", + Image: images.EntrypointImage, + WorkingDir: "/", + Command: []string{"/ko-app/entrypoint", "init", "/ko-app/entrypoint", entrypointBinary, "step-foo", "step-bar"}, + VolumeMounts: []corev1.VolumeMount{binMount, internalStepsMount}, + }, + }, { + name: "nothing-special-two-steps-windows-security-context", + steps: []v1beta1.Step{{ + Name: "foo", + }, { + Name: "bar", + }}, + setSecurityContext: true, + windows: true, + want: corev1.Container{ + Name: "prepare", + Image: images.EntrypointImage, + WorkingDir: "/", + Command: []string{"/ko-app/entrypoint", "init", "/ko-app/entrypoint", entrypointBinary, "step-foo", "step-bar"}, + VolumeMounts: []corev1.VolumeMount{binMount, internalStepsMount}, + SecurityContext: windowsSecurityContext, + }, }} for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - container := entrypointInitContainer(images.EntrypointImage, tc.steps) + container := entrypointInitContainer(images.EntrypointImage, tc.steps, tc.setSecurityContext, tc.windows) if d := cmp.Diff(tc.want, container); d != "" { t.Errorf("Diff %s", diff.PrintWantGot(d)) } }) } } + +func TestUsesWindows(t *testing.T) { + tcs := []struct { + name string + taskRun *v1beta1.TaskRun + want bool + }{{ + name: "no pod template", + taskRun: &v1beta1.TaskRun{Spec: v1beta1.TaskRunSpec{}}, + want: false, + }, { + name: "pod template w/out node selector", + taskRun: &v1beta1.TaskRun{Spec: v1beta1.TaskRunSpec{PodTemplate: &pod.Template{Env: []corev1.EnvVar{{Name: "foo", Value: "bar"}}}}}, + want: false, + }, { + name: "uses linux", + taskRun: &v1beta1.TaskRun{Spec: v1beta1.TaskRunSpec{PodTemplate: &pod.Template{NodeSelector: map[string]string{ + osSelectorLabel: "linux", + }}}}, + want: false, + }, { + name: "uses windows", + taskRun: &v1beta1.TaskRun{Spec: v1beta1.TaskRunSpec{PodTemplate: &pod.Template{NodeSelector: map[string]string{ + osSelectorLabel: "windows", + }}}}, + want: true, + }} + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + got := usesWindows(tc.taskRun) + if tc.want != got { + t.Errorf("wanted usesWindows to be %t but was %t", tc.want, got) + } + }) + } +} diff --git a/pkg/pod/script.go b/pkg/pod/script.go index a984cd72ca8..87edd1b5296 100644 --- a/pkg/pod/script.go +++ b/pkg/pod/script.go @@ -69,7 +69,7 @@ var ( ) // convertScripts converts any steps and sidecars that specify a Script field into a normal Container. -func convertScripts(shellImageLinux string, shellImageWin string, steps []v1beta1.Step, sidecars []v1beta1.Sidecar, debugConfig *v1beta1.TaskRunDebug) (*corev1.Container, []corev1.Container, []corev1.Container) { +func convertScripts(shellImageLinux string, shellImageWin string, steps []v1beta1.Step, sidecars []v1beta1.Sidecar, debugConfig *v1beta1.TaskRunDebug, setSecurityContext bool) (*corev1.Container, []corev1.Container, []corev1.Container) { // Place scripts is an init container used for creating scripts in the // /tekton/scripts directory which would be later used by the step containers // as a Command @@ -78,11 +78,13 @@ func convertScripts(shellImageLinux string, shellImageWin string, steps []v1beta shellImage := shellImageLinux shellCommand := "sh" shellArg := "-c" + securityContext := linuxSecurityContext // Set windows variants for Image, Command and Args if requiresWindows { shellImage = shellImageWin shellCommand = "pwsh" shellArg = "-Command" + securityContext = windowsSecurityContext } placeScriptsInit := corev1.Container{ @@ -92,6 +94,9 @@ func convertScripts(shellImageLinux string, shellImageWin string, steps []v1beta Args: []string{shellArg, ""}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, } + if setSecurityContext { + placeScriptsInit.SecurityContext = securityContext + } breakpoints := []string{} diff --git a/pkg/pod/script_test.go b/pkg/pod/script_test.go index 3f9f8d0be08..99db1dbdbd9 100644 --- a/pkg/pod/script_test.go +++ b/pkg/pod/script_test.go @@ -31,7 +31,7 @@ func TestConvertScripts_NothingToConvert_EmptySidecars(t *testing.T) { Image: "step-1", }, { Image: "step-2", - }}, []v1beta1.Sidecar{}, nil) + }}, []v1beta1.Sidecar{}, nil, false) want := []corev1.Container{{ Image: "step-1", }, { @@ -54,7 +54,7 @@ func TestConvertScripts_NothingToConvert_NilSidecars(t *testing.T) { Image: "step-1", }, { Image: "step-2", - }}, nil, nil) + }}, nil, nil, false) want := []corev1.Container{{ Image: "step-1", }, { @@ -79,7 +79,7 @@ func TestConvertScripts_NothingToConvert_WithSidecar(t *testing.T) { Image: "step-2", }}, []v1beta1.Sidecar{{ Image: "sidecar-1", - }}, nil) + }}, nil, false) want := []corev1.Container{{ Image: "step-1", }, { @@ -107,7 +107,6 @@ func TestConvertScripts_NothingToConvert_WithSidecar(t *testing.T) { func TestConvertScripts_Steps(t *testing.T) { names.TestingSeed() - preExistingVolumeMounts := []corev1.VolumeMount{{ Name: "pre-existing-volume-mount", MountPath: "/mount/path", @@ -135,7 +134,7 @@ script-3`, Image: "step-3", VolumeMounts: preExistingVolumeMounts, Args: []string{"my", "args"}, - }}, []v1beta1.Sidecar{}, nil) + }}, []v1beta1.Sidecar{}, nil, true) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImage, @@ -159,7 +158,8 @@ IyEvYmluL3NoCnNldCAtZQpuby1zaGViYW5n _EOF_ /tekton/bin/entrypoint decode-script "${scriptfile}" `}, - VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + SecurityContext: linuxSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -335,7 +335,7 @@ _EOF_ }}, }} { t.Run(tc.name, func(t *testing.T) { - gotInit, gotSteps, gotSidecars := convertScripts(images.ShellImage, images.ShellImageWin, []v1beta1.Step{}, tc.sidecars, nil) + gotInit, gotSteps, gotSidecars := convertScripts(images.ShellImage, images.ShellImageWin, []v1beta1.Step{}, tc.sidecars, nil, false) gotInitScripts := "" if gotInit != nil { gotInitScripts = gotInit.Args[1] @@ -385,7 +385,7 @@ script-3`, Args: []string{"my", "args"}, }}, []v1beta1.Sidecar{}, &v1beta1.TaskRunDebug{ Breakpoint: []string{breakpointOnFailure}, - }) + }, true) wantInit := &corev1.Container{ Name: "place-scripts", @@ -454,7 +454,8 @@ else fi debug-fail-continue-heredoc-randomly-generated-6nl7g `}, - VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount, debugScriptsVolumeMount}, + VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount, debugScriptsVolumeMount}, + SecurityContext: linuxSecurityContext, } want := []corev1.Container{{ @@ -527,7 +528,7 @@ script-3`, Script: `#!/bin/sh sidecar-1`, Image: "sidecar-1", - }}, nil) + }}, nil, true) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImage, @@ -551,7 +552,8 @@ IyEvYmluL3NoCnNpZGVjYXItMQ== _EOF_ /tekton/bin/entrypoint decode-script "${scriptfile}" `}, - VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + SecurityContext: linuxSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -620,7 +622,7 @@ no-shebang`, Image: "step-3", VolumeMounts: preExistingVolumeMounts, Args: []string{"my", "args"}, - }}, []v1beta1.Sidecar{}, nil) + }}, []v1beta1.Sidecar{}, nil, true) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImageWin, @@ -637,7 +639,8 @@ script-3 no-shebang "@ | Out-File -FilePath /tekton/scripts/script-3-mssqb.cmd `}, - VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + SecurityContext: windowsSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -701,7 +704,7 @@ script-3`, Script: `#!win pwsh -File sidecar-1`, Image: "sidecar-1", - }}, nil) + }}, nil, true) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImageWin, @@ -719,7 +722,8 @@ script-3 sidecar-1 "@ | Out-File -FilePath /tekton/scripts/sidecar-script-0-mssqb `}, - VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + SecurityContext: windowsSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -770,7 +774,7 @@ func TestConvertScripts_Windows_SidecarOnly(t *testing.T) { Script: `#!win python sidecar-1`, Image: "sidecar-1", - }}, nil) + }}, nil, true) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImageWin, @@ -780,7 +784,8 @@ sidecar-1`, sidecar-1 "@ | Out-File -FilePath /tekton/scripts/sidecar-script-0-9l9zj `}, - VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, + SecurityContext: windowsSecurityContext, } want := []corev1.Container{{ Image: "step-1", diff --git a/pkg/pod/workingdir_init.go b/pkg/pod/workingdir_init.go index 6a32c7cc787..ff369b4bc52 100644 --- a/pkg/pod/workingdir_init.go +++ b/pkg/pod/workingdir_init.go @@ -31,7 +31,7 @@ import ( // // If no such directories need to be created (i.e., no relative workingDirs // are specified), this method returns nil, as no init container is necessary. -func workingDirInit(workingdirinitImage string, stepContainers []corev1.Container) *corev1.Container { +func workingDirInit(workingdirinitImage string, stepContainers []corev1.Container, setSecurityContext, windows bool) *corev1.Container { // Gather all unique workingDirs. workingDirs := sets.NewString() for _, step := range stepContainers { @@ -56,8 +56,12 @@ func workingDirInit(workingdirinitImage string, stepContainers []corev1.Containe // There are no workingDirs to initialize. return nil } + securityContext := linuxSecurityContext + if windows { + securityContext = windowsSecurityContext + } - return &corev1.Container{ + c := &corev1.Container{ Name: "working-dir-initializer", Image: workingdirinitImage, Command: []string{"/ko-app/workingdirinit"}, @@ -65,4 +69,8 @@ func workingDirInit(workingdirinitImage string, stepContainers []corev1.Containe WorkingDir: pipeline.WorkspaceDir, VolumeMounts: implicitVolumeMounts, } + if setSecurityContext { + c.SecurityContext = securityContext + } + return c } diff --git a/pkg/pod/workingdir_init_test.go b/pkg/pod/workingdir_init_test.go index b3844da1558..05f1f65f133 100644 --- a/pkg/pod/workingdir_init_test.go +++ b/pkg/pod/workingdir_init_test.go @@ -29,9 +29,11 @@ import ( func TestWorkingDirInit(t *testing.T) { names.TestingSeed() for _, c := range []struct { - desc string - stepContainers []corev1.Container - want *corev1.Container + desc string + stepContainers []corev1.Container + windows bool + setSecurityContext bool + want *corev1.Container }{{ desc: "no workingDirs", stepContainers: []corev1.Container{{ @@ -63,9 +65,90 @@ func TestWorkingDirInit(t *testing.T) { WorkingDir: pipeline.WorkspaceDir, VolumeMounts: implicitVolumeMounts, }, + }, { + desc: "workingDirs are unique and sorted, absolute dirs are ignored, + securitycontext", + stepContainers: []corev1.Container{{ + WorkingDir: "zzz", + }, { + WorkingDir: "aaa", + }, { + WorkingDir: "/ignored", + }, { + WorkingDir: "/workspace", // ignored + }, { + WorkingDir: "zzz", + }, { + // Even though it's specified absolute, it's relative + // to /workspace, so we need to create it. + WorkingDir: "/workspace/bbb", + }}, + setSecurityContext: true, + want: &corev1.Container{ + Name: "working-dir-initializer", + Image: images.WorkingDirInitImage, + Command: []string{"/ko-app/workingdirinit"}, + Args: []string{"/workspace/bbb", "aaa", "zzz"}, + WorkingDir: pipeline.WorkspaceDir, + VolumeMounts: implicitVolumeMounts, + SecurityContext: linuxSecurityContext, + }, + }, { + desc: "workingDirs are unique and sorted, absolute dirs are ignored, uses windows", + stepContainers: []corev1.Container{{ + WorkingDir: "zzz", + }, { + WorkingDir: "aaa", + }, { + WorkingDir: "/ignored", + }, { + WorkingDir: "/workspace", // ignored + }, { + WorkingDir: "zzz", + }, { + // Even though it's specified absolute, it's relative + // to /workspace, so we need to create it. + WorkingDir: "/workspace/bbb", + }}, + windows: true, + want: &corev1.Container{ + Name: "working-dir-initializer", + Image: images.WorkingDirInitImage, + Command: []string{"/ko-app/workingdirinit"}, + Args: []string{"/workspace/bbb", "aaa", "zzz"}, + WorkingDir: pipeline.WorkspaceDir, + VolumeMounts: implicitVolumeMounts, + }, + }, { + desc: "workingDirs are unique and sorted, absolute dirs are ignored, uses windows, + securityContext", + stepContainers: []corev1.Container{{ + WorkingDir: "zzz", + }, { + WorkingDir: "aaa", + }, { + WorkingDir: "/ignored", + }, { + WorkingDir: "/workspace", // ignored + }, { + WorkingDir: "zzz", + }, { + // Even though it's specified absolute, it's relative + // to /workspace, so we need to create it. + WorkingDir: "/workspace/bbb", + }}, + windows: true, + setSecurityContext: true, + want: &corev1.Container{ + Name: "working-dir-initializer", + Image: images.WorkingDirInitImage, + Command: []string{"/ko-app/workingdirinit"}, + Args: []string{"/workspace/bbb", "aaa", "zzz"}, + WorkingDir: pipeline.WorkspaceDir, + VolumeMounts: implicitVolumeMounts, + SecurityContext: windowsSecurityContext, + }, }} { t.Run(c.desc, func(t *testing.T) { - got := workingDirInit(images.WorkingDirInitImage, c.stepContainers) + got := workingDirInit(images.WorkingDirInitImage, c.stepContainers, c.setSecurityContext, c.windows) if d := cmp.Diff(c.want, got); d != "" { t.Fatalf("Diff %s", diff.PrintWantGot(d)) }