diff --git a/config/config-feature-flags.yaml b/config/config-feature-flags.yaml index 398f37d1880..dd5e6e2ec1f 100644 --- a/config/config-feature-flags.yaml +++ b/config/config-feature-flags.yaml @@ -117,6 +117,8 @@ data: # This allows TaskRuns to run in namespaces with "restricted" pod security standards. # Not all Kubernetes implementations support this option. set-security-context: "false" + # Setting this flag to "true" will set root filesystemt to readonly for containers injected by Tekton into TaskRuns. + set-security-context-read-only-root-filesystem: "false" # Setting this flag to "true" will keep pod on cancellation # allowing examination of the logs on the pods from cancelled taskruns keep-pod-on-cancel: "false" diff --git a/docs/additional-configs.md b/docs/additional-configs.md index e71f09123e4..b493ee7fd7a 100644 --- a/docs/additional-configs.md +++ b/docs/additional-configs.md @@ -360,6 +360,11 @@ Defaults to "ignore". - `set-security-context`: Set this flag to `true` to set a security context for containers injected by Tekton that will allow TaskRun pods to run in namespaces with `restricted` pod security admission. By default, this is set to `false`. +- `set-security-context-read-only-root-filesystem`: Set this flag to `true` to enable `readOnlyRootFilesystem` in the + security context for containers injected by Tekton. This makes the root filesystem of the container read-only, + enhancing security. Note that this requires `set-security-context` to be enabled. By default, this flag is set + to `false`. Note: This feature does not work in windows as it is not supported there, [Comparison with linux](https://kubernetes.io/docs/concepts/windows/intro/#compatibility-linux-similarities). + ### Alpha Features Alpha features in the following table are still in development and their syntax is subject to change. diff --git a/pkg/apis/config/feature_flags.go b/pkg/apis/config/feature_flags.go index d0db62052cf..396e52c1b12 100644 --- a/pkg/apis/config/feature_flags.go +++ b/pkg/apis/config/feature_flags.go @@ -94,6 +94,8 @@ const ( DefaultMaxResultSize = 4096 // DefaultSetSecurityContext is the default value for "set-security-context" DefaultSetSecurityContext = false + // DefaultSetSecurityContextReadOnlyRootFilesystem is the default value for "set-security-context-read-only-root-filesystem" + DefaultSetSecurityContextReadOnlyRootFilesystem = false // DefaultCoschedule is the default value for coschedule DefaultCoschedule = CoscheduleWorkspaces // KeepPodOnCancel is the flag used to enable cancelling a pod using the entrypoint, and keep pod on cancel @@ -123,15 +125,16 @@ const ( awaitSidecarReadinessKey = "await-sidecar-readiness" requireGitSSHSecretKnownHostsKey = "require-git-ssh-secret-known-hosts" //nolint:gosec // enableTektonOCIBundles = "enable-tekton-oci-bundles" - enableAPIFields = "enable-api-fields" - sendCloudEventsForRuns = "send-cloudevents-for-runs" - enforceNonfalsifiability = "enforce-nonfalsifiability" - verificationNoMatchPolicy = "trusted-resources-verification-no-match-policy" - enableProvenanceInStatus = "enable-provenance-in-status" - resultExtractionMethod = "results-from" - maxResultSize = "max-result-size" - setSecurityContextKey = "set-security-context" - coscheduleKey = "coschedule" + enableAPIFields = "enable-api-fields" + sendCloudEventsForRuns = "send-cloudevents-for-runs" + enforceNonfalsifiability = "enforce-nonfalsifiability" + verificationNoMatchPolicy = "trusted-resources-verification-no-match-policy" + enableProvenanceInStatus = "enable-provenance-in-status" + resultExtractionMethod = "results-from" + maxResultSize = "max-result-size" + setSecurityContextKey = "set-security-context" + setSecurityContextReadOnlyRootFilesystemKey = "set-security-context-read-only-root-filesystem" + coscheduleKey = "coschedule" ) // DefaultFeatureFlags holds all the default configurations for the feature flags configmap. @@ -200,19 +203,20 @@ type FeatureFlags struct { // ignore: skip trusted resources verification when no matching verification policies found // warn: skip trusted resources verification when no matching verification policies found and log a warning // fail: fail the taskrun or pipelines run if no matching verification policies found - VerificationNoMatchPolicy string - EnableProvenanceInStatus bool - ResultExtractionMethod string - MaxResultSize int - SetSecurityContext bool - Coschedule string - EnableCELInWhenExpression bool - EnableStepActions bool - EnableParamEnum bool - EnableArtifacts bool - DisableInlineSpec string - EnableConciseResolverSyntax bool - EnableKubernetesSidecar bool + VerificationNoMatchPolicy string + EnableProvenanceInStatus bool + ResultExtractionMethod string + MaxResultSize int + SetSecurityContext bool + SetSecurityContextReadOnlyRootFilesystem bool + Coschedule string + EnableCELInWhenExpression bool + EnableStepActions bool + EnableParamEnum bool + EnableArtifacts bool + DisableInlineSpec string + EnableConciseResolverSyntax bool + EnableKubernetesSidecar bool } // GetFeatureFlagsConfigName returns the name of the configmap containing all @@ -295,6 +299,9 @@ func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) { if err := setFeature(setSecurityContextKey, DefaultSetSecurityContext, &tc.SetSecurityContext); err != nil { return nil, err } + if err := setFeature(setSecurityContextReadOnlyRootFilesystemKey, DefaultSetSecurityContextReadOnlyRootFilesystem, &tc.SetSecurityContextReadOnlyRootFilesystem); err != nil { + return nil, err + } if err := setCoschedule(cfgMap, DefaultCoschedule, tc.DisableAffinityAssistant, &tc.Coschedule); err != nil { return nil, err } diff --git a/pkg/apis/config/feature_flags_test.go b/pkg/apis/config/feature_flags_test.go index c9eca976953..21d467d2b89 100644 --- a/pkg/apis/config/feature_flags_test.go +++ b/pkg/apis/config/feature_flags_test.go @@ -63,27 +63,28 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) { }, { expectedConfig: &config.FeatureFlags{ - DisableAffinityAssistant: true, - RunningInEnvWithInjectedSidecars: false, - AwaitSidecarReadiness: false, - RequireGitSSHSecretKnownHosts: true, - EnableAPIFields: "alpha", - SendCloudEventsForRuns: true, - EnforceNonfalsifiability: "spire", - VerificationNoMatchPolicy: config.FailNoMatchPolicy, - EnableProvenanceInStatus: false, - ResultExtractionMethod: "termination-message", - EnableKeepPodOnCancel: true, - MaxResultSize: 4096, - SetSecurityContext: true, - Coschedule: config.CoscheduleDisabled, - EnableCELInWhenExpression: true, - EnableStepActions: true, - EnableArtifacts: true, - EnableParamEnum: true, - DisableInlineSpec: "pipeline,pipelinerun,taskrun", - EnableConciseResolverSyntax: true, - EnableKubernetesSidecar: true, + DisableAffinityAssistant: true, + RunningInEnvWithInjectedSidecars: false, + AwaitSidecarReadiness: false, + RequireGitSSHSecretKnownHosts: true, + EnableAPIFields: "alpha", + SendCloudEventsForRuns: true, + EnforceNonfalsifiability: "spire", + VerificationNoMatchPolicy: config.FailNoMatchPolicy, + EnableProvenanceInStatus: false, + ResultExtractionMethod: "termination-message", + EnableKeepPodOnCancel: true, + MaxResultSize: 4096, + SetSecurityContext: true, + SetSecurityContextReadOnlyRootFilesystem: true, + Coschedule: config.CoscheduleDisabled, + EnableCELInWhenExpression: true, + EnableStepActions: true, + EnableArtifacts: true, + EnableParamEnum: true, + DisableInlineSpec: "pipeline,pipelinerun,taskrun", + EnableConciseResolverSyntax: true, + EnableKubernetesSidecar: true, }, fileName: "feature-flags-all-flags-set", }, 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 a01101604a1..e16b67f1db2 100644 --- a/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml +++ b/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml @@ -31,6 +31,7 @@ data: trusted-resources-verification-no-match-policy: "fail" enable-provenance-in-status: "false" set-security-context: "true" + set-security-context-read-only-root-filesystem: "true" keep-pod-on-cancel: "true" enable-cel-in-whenexpression: "true" enable-step-actions: "true" diff --git a/pkg/internal/affinityassistant/affinityassistant_types.go b/pkg/internal/affinityassistant/affinityassistant_types.go index 15afe705b98..a2c0fe622ec 100644 --- a/pkg/internal/affinityassistant/affinityassistant_types.go +++ b/pkg/internal/affinityassistant/affinityassistant_types.go @@ -17,6 +17,8 @@ import ( "context" "fmt" + "github.com/tektoncd/pipeline/pkg/pod" + "github.com/tektoncd/pipeline/pkg/apis/config" ) @@ -57,6 +59,6 @@ func GetAffinityAssistantBehavior(ctx context.Context) (AffinityAssistantBehavio // ContainerConfig defines AffinityAssistant container configuration type ContainerConfig struct { - Image string - SetSecurityContext bool + Image string + SecurityContextConfig pod.SecurityContextConfig } diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go index 31cc2fb9081..cd5889915c3 100644 --- a/pkg/pod/pod.go +++ b/pkg/pod/pod.go @@ -127,27 +127,6 @@ 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 - - // LinuxSecurityContext 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. @@ -178,6 +157,7 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1.TaskRun, taskSpec v1.Ta sidecarLogsResultsEnabled := config.FromContextOrDefaults(ctx).FeatureFlags.ResultExtractionMethod == config.ResultExtractionMethodSidecarLogs enableKeepPodOnCancel := featureFlags.EnableKeepPodOnCancel setSecurityContext := config.FromContextOrDefaults(ctx).FeatureFlags.SetSecurityContext + setSecurityContextReadOnlyRootFilesystem := config.FromContextOrDefaults(ctx).FeatureFlags.SetSecurityContextReadOnlyRootFilesystem // Add our implicit volumes first, so they can be overridden by the user if they prefer. volumes = append(volumes, implicitVolumes...) @@ -212,11 +192,17 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1.TaskRun, taskSpec v1.Ta if taskRun.Spec.ComputeResources != nil { tasklevel.ApplyTaskLevelComputeResources(steps, taskRun.Spec.ComputeResources) } + + securityContextConfig := SecurityContextConfig{ + SetSecurityContext: setSecurityContext, + SetReadOnlyRootFilesystem: setSecurityContextReadOnlyRootFilesystem, + } + windows := usesWindows(taskRun) if sidecarLogsResultsEnabled { if taskSpec.Results != nil || artifactsPathReferenced(steps) { // create a results sidecar - resultsSidecar, err := createResultsSidecar(taskSpec, b.Images.SidecarLogResultsImage, setSecurityContext, windows) + resultsSidecar, err := createResultsSidecar(taskSpec, b.Images.SidecarLogResultsImage, securityContextConfig, windows) if err != nil { return nil, err } @@ -231,15 +217,15 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1.TaskRun, taskSpec v1.Ta } initContainers = []corev1.Container{ - entrypointInitContainer(b.Images.EntrypointImage, steps, setSecurityContext, windows), + entrypointInitContainer(b.Images.EntrypointImage, steps, securityContextConfig, 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, setSecurityContext) + scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, b.Images.ShellImageWin, steps, sidecars, taskRun.Spec.Debug, securityContextConfig) } else { - scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, "", steps, sidecars, nil, setSecurityContext) + scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, "", steps, sidecars, nil, securityContextConfig) } if scriptsInit != nil { @@ -250,7 +236,7 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1.TaskRun, taskSpec v1.Ta volumes = append(volumes, debugScriptsVolume, debugInfoVolume) } // Initialize any workingDirs under /workspace. - if workingDirInit := workingDirInit(b.Images.WorkingDirInitImage, stepContainers, setSecurityContext, windows); workingDirInit != nil { + if workingDirInit := workingDirInit(b.Images.WorkingDirInitImage, stepContainers, securityContextConfig, windows); workingDirInit != nil { initContainers = append(initContainers, *workingDirInit) } @@ -695,7 +681,7 @@ func runVolume(i int) corev1.Volume { // This should effectively merge multiple command and volumes together. // If setSecurityContext is true, the init container will include a security context // allowing it to run in namespaces with restriced pod security admission. -func entrypointInitContainer(image string, steps []v1.Step, setSecurityContext, windows bool) corev1.Container { +func entrypointInitContainer(image string, steps []v1.Step, securityContext SecurityContextConfig, 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} @@ -703,10 +689,6 @@ func entrypointInitContainer(image string, steps []v1.Step, setSecurityContext, 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 @@ -721,8 +703,8 @@ func entrypointInitContainer(image string, steps []v1.Step, setSecurityContext, Command: command, VolumeMounts: volumeMounts, } - if setSecurityContext { - prepareInitContainer.SecurityContext = securityContext + if securityContext.SetSecurityContext { + prepareInitContainer.SecurityContext = securityContext.GetSecurityContext(windows) } return prepareInitContainer } @@ -732,7 +714,7 @@ func entrypointInitContainer(image string, steps []v1.Step, setSecurityContext, // whether it will run on a windows node, and whether the sidecar should include a security context // that will allow it to run in namespaces with "restricted" pod security admission. // It will also provide arguments to the binary that allow it to surface the step results. -func createResultsSidecar(taskSpec v1.TaskSpec, image string, setSecurityContext, windows bool) (v1.Sidecar, error) { +func createResultsSidecar(taskSpec v1.TaskSpec, image string, securityContext SecurityContextConfig, windows bool) (v1.Sidecar, error) { names := make([]string, 0, len(taskSpec.Results)) for _, r := range taskSpec.Results { names = append(names, r.Name) @@ -775,13 +757,11 @@ func createResultsSidecar(taskSpec v1.TaskSpec, image string, setSecurityContext Image: image, Command: command, } - securityContext := LinuxSecurityContext - if windows { - securityContext = WindowsSecurityContext - } - if setSecurityContext { - sidecar.SecurityContext = securityContext + + if securityContext.SetSecurityContext { + sidecar.SecurityContext = securityContext.GetSecurityContext(windows) } + return sidecar, nil } diff --git a/pkg/pod/pod_test.go b/pkg/pod/pod_test.go index da6a32644e7..3073b3a195b 100644 --- a/pkg/pod/pod_test.go +++ b/pkg/pod/pod_test.go @@ -112,7 +112,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -162,7 +162,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -208,7 +208,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -255,7 +255,7 @@ func TestPodBuild(t *testing.T) { want: &corev1.PodSpec{ ServiceAccountName: "service-account", RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -323,7 +323,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -381,7 +381,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "a-very-very-long-character-step-name-to-trigger-max-len----and-invalid-characters"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "a-very-very-long-character-step-name-to-trigger-max-len----and-invalid-characters"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-a-very-very-long-character-step-name-to-trigger-max-len", // step name trimmed. Image: "image", @@ -424,7 +424,7 @@ func TestPodBuild(t *testing.T) { }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "ends-with-invalid-%%__$$"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "ends-with-invalid-%%__$$"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-ends-with-invalid", // invalid suffix removed. Image: "image", @@ -469,7 +469,7 @@ func TestPodBuild(t *testing.T) { want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), { Name: "working-dir-initializer", Image: images.WorkingDirInitImage, @@ -527,7 +527,7 @@ func TestPodBuild(t *testing.T) { wantAnnotations: map[string]string{}, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "primary-name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-primary-name", Image: "primary-image", @@ -583,7 +583,7 @@ func TestPodBuild(t *testing.T) { want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "primary-name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), { Name: "place-scripts", Image: "busybox", @@ -653,7 +653,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, []v1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "primary-name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-primary-name", Image: "primary-image", @@ -716,7 +716,7 @@ _EOF_ InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{ {Name: "unnamed-0"}, {Name: "unnamed-1"}, - }, false /* setSecurityContext */, false /* windows */)}, + }, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-unnamed-0", Image: "image", @@ -815,7 +815,7 @@ _EOF_ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{ {Name: "step1"}, - }, false /* setSecurityContext */, false /* windows */)}, + }, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-step1", Image: "image", @@ -885,7 +885,7 @@ _EOF_ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{ {Name: "step1"}, - }, false /* setSecurityContext */, false /* windows */)}, + }, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-step1", Image: "image", @@ -956,7 +956,7 @@ _EOF_ wantAnnotations: map[string]string{}, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "primary-name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "primary-name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-primary-name", Image: "primary-image", @@ -1027,7 +1027,7 @@ print("Hello from Python")`, {Name: "one"}, {Name: "two"}, {Name: "regular-step"}, - }, false /* setSecurityContext */, false /* windows */), + }, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), { Name: "place-scripts", Image: images.ShellImage, @@ -1151,7 +1151,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "one"}}, false /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "one"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), { Name: "place-scripts", Image: images.ShellImage, @@ -1215,7 +1215,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "schedule-me"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "schedule-me"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, SchedulerName: "there-scheduler", Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -1267,7 +1267,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "image-pull"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "image-pull"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, 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}}, @@ -1318,7 +1318,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "host-aliases"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "host-aliases"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, 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}}, @@ -1369,7 +1369,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "use-my-hostNetwork"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "use-my-hostNetwork"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, HostNetwork: true, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -1414,7 +1414,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1463,7 +1463,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1511,7 +1511,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1561,7 +1561,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1617,7 +1617,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1669,7 +1669,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1720,7 +1720,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1787,7 +1787,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1833,7 +1833,7 @@ _EOF_ wantPodName: "task-run-0123456789-01234560d38957287bb0283c59440df14069f59-pod", want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -1894,7 +1894,7 @@ _EOF_ }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "use-topologySpreadConstraints"}}, false /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "use-topologySpreadConstraints"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)}, TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ { MaxSkew: 1, @@ -1955,7 +1955,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2034,7 +2034,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2096,7 +2096,7 @@ _EOF_ }, { desc: "sidecar logs enabled and artifacts not enabled, set security context is true", - featureFlags: map[string]string{"results-from": "sidecar-logs", "set-security-context": "true"}, + featureFlags: map[string]string{"results-from": "sidecar-logs", "set-security-context": "true", "set-security-context-read-only-root-filesystem": "true"}, ts: v1.TaskSpec{ Results: []v1.TaskResult{{ Name: "foo", @@ -2111,7 +2111,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, true /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2161,7 +2161,7 @@ _EOF_ {Name: "tekton-internal-bin", ReadOnly: true, MountPath: "/tekton/bin"}, {Name: "tekton-internal-run-0", ReadOnly: true, MountPath: "/tekton/run/0"}, }, implicitVolumeMounts...), - SecurityContext: LinuxSecurityContext, + SecurityContext: SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}.GetSecurityContext(false), }}, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -2187,7 +2187,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2269,7 +2269,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2334,7 +2334,7 @@ _EOF_ }, { desc: "sidecar logs enabled, artifacts referenced and security context set ", - featureFlags: map[string]string{"results-from": "sidecar-logs", "set-security-context": "true", "enable-artifacts": "true"}, + featureFlags: map[string]string{"results-from": "sidecar-logs", "set-security-context": "true", "set-security-context-read-only-root-filesystem": "true", "enable-artifacts": "true"}, ts: v1.TaskSpec{ Results: []v1.TaskResult{{ Name: "foo", @@ -2349,7 +2349,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, true /* setSecurityContext */, false /* windows */), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2402,7 +2402,7 @@ _EOF_ {Name: "tekton-internal-bin", ReadOnly: true, MountPath: "/tekton/bin"}, {Name: "tekton-internal-run-0", ReadOnly: true, MountPath: "/tekton/run/0"}, }, implicitVolumeMounts...), - SecurityContext: LinuxSecurityContext, + SecurityContext: SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}.GetSecurityContext(false), }}, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -2420,10 +2420,10 @@ _EOF_ Command: []string{"cmd"}, // avoid entrypoint lookup. }}, }, - featureFlags: map[string]string{"set-security-context": "true"}, + featureFlags: map[string]string{"set-security-context": "true", "set-security-context-read-only-root-filesystem": "true"}, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, true /* setSecurityContext */, false /* windows */)}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}, false /* windows */)}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -2468,7 +2468,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false, false), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2526,7 +2526,7 @@ _EOF_ want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, InitContainers: []corev1.Container{ - entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false, false), + entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false), }, Containers: []corev1.Container{{ Name: "step-name", @@ -2757,7 +2757,7 @@ debug-fail-continue-heredoc-randomly-generated-mz4c7 }, want: &corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, - InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */), placeScriptsContainer}, + InitContainers: []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */), placeScriptsContainer}, Containers: []corev1.Container{{ Name: "step-name", Image: "image", @@ -3070,7 +3070,7 @@ func TestPodBuild_TaskLevelResourceRequirements(t *testing.T) { } func TestPodBuildwithSpireEnabled(t *testing.T) { - initContainers := []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, false /* setSecurityContext */, false /* windows */)} + initContainers := []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1.Step{{Name: "name"}}, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}, false /* windows */)} readonly := true for i := range initContainers { c := &initContainers[i] @@ -3374,12 +3374,13 @@ func TestIsPodReadyImmediately(t *testing.T) { func TestPrepareInitContainers(t *testing.T) { tcs := []struct { - name string - steps []v1.Step - windows bool - setSecurityContext bool - want corev1.Container - featureFlags map[string]string + name string + steps []v1.Step + windows bool + setSecurityContext bool + setSecurityContextReadOnlyRootFilesystem bool + want corev1.Container + featureFlags map[string]string }{{ name: "nothing-special", steps: []v1.Step{{ @@ -3413,14 +3414,18 @@ func TestPrepareInitContainers(t *testing.T) { }, { Name: "bar", }}, - setSecurityContext: true, + setSecurityContext: true, + setSecurityContextReadOnlyRootFilesystem: 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: "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: SecurityContextConfig{ + SetSecurityContext: true, + SetReadOnlyRootFilesystem: true, + }.GetSecurityContext(false), }, }, { name: "nothing-special-two-steps-windows", @@ -3444,8 +3449,9 @@ func TestPrepareInitContainers(t *testing.T) { }, { Name: "bar", }}, - setSecurityContext: true, - windows: true, + setSecurityContext: true, + setSecurityContextReadOnlyRootFilesystem: true, + windows: true, want: corev1.Container{ Name: "prepare", Image: images.EntrypointImage, @@ -3457,7 +3463,11 @@ func TestPrepareInitContainers(t *testing.T) { }} for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - container := entrypointInitContainer(images.EntrypointImage, tc.steps, tc.setSecurityContext, tc.windows) + securityContextConfig := SecurityContextConfig{ + SetSecurityContext: tc.setSecurityContext, + SetReadOnlyRootFilesystem: tc.setSecurityContextReadOnlyRootFilesystem, + } + container := entrypointInitContainer(images.EntrypointImage, tc.steps, securityContextConfig, tc.windows) if d := cmp.Diff(tc.want, container); d != "" { t.Errorf("Diff %s", diff.PrintWantGot(d)) } @@ -4021,7 +4031,10 @@ func TestPodBuildWithK8s129(t *testing.T) { entrypointInitContainer( images.EntrypointImage, []v1.Step{{Name: "name"}}, - false, /* setSecurityContext */ + SecurityContextConfig{ + SetSecurityContext: false, + SetReadOnlyRootFilesystem: false, + }, false /* windows */), { Name: "sidecar-name", diff --git a/pkg/pod/script.go b/pkg/pod/script.go index efcbc283858..4ac5202ab7b 100644 --- a/pkg/pod/script.go +++ b/pkg/pod/script.go @@ -78,7 +78,7 @@ var ( // - debugConfig: the TaskRun's debug configuration // - setSecurityContext: whether the init container should include a security context that will // allow it to run in a namespace with "restricted" pod security admission -func convertScripts(shellImageLinux string, shellImageWin string, steps []v1.Step, sidecars []v1.Sidecar, debugConfig *v1.TaskRunDebug, setSecurityContext bool) (*corev1.Container, []corev1.Container, []corev1.Container) { +func convertScripts(shellImageLinux string, shellImageWin string, steps []v1.Step, sidecars []v1.Sidecar, debugConfig *v1.TaskRunDebug, securityContext SecurityContextConfig) (*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 @@ -87,13 +87,12 @@ func convertScripts(shellImageLinux string, shellImageWin string, steps []v1.Ste 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{ @@ -103,8 +102,9 @@ func convertScripts(shellImageLinux string, shellImageWin string, steps []v1.Ste Args: []string{shellArg, ""}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, } - if setSecurityContext { - placeScriptsInit.SecurityContext = securityContext + + if securityContext.SetSecurityContext { + placeScriptsInit.SecurityContext = securityContext.GetSecurityContext(requiresWindows) } // Add mounts for debug diff --git a/pkg/pod/script_test.go b/pkg/pod/script_test.go index b8ce120676e..c62086eda41 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", - }}, []v1.Sidecar{}, nil, false) + }}, []v1.Sidecar{}, nil, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: 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, false) + }}, nil, nil, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}) want := []corev1.Container{{ Image: "step-1", }, { @@ -79,7 +79,7 @@ func TestConvertScripts_NothingToConvert_WithSidecar(t *testing.T) { Image: "step-2", }}, []v1.Sidecar{{ Image: "sidecar-1", - }}, nil, false) + }}, nil, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}) want := []corev1.Container{{ Image: "step-1", }, { @@ -134,7 +134,7 @@ script-3`, Image: "step-3", VolumeMounts: preExistingVolumeMounts, Args: []string{"my", "args"}, - }}, []v1.Sidecar{}, nil, true) + }}, []v1.Sidecar{}, nil, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImage, @@ -159,7 +159,7 @@ _EOF_ /tekton/bin/entrypoint decode-script "${scriptfile}" `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, - SecurityContext: LinuxSecurityContext, + SecurityContext: SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}.GetSecurityContext(false), } 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, []v1.Step{}, tc.sidecars, nil, false) + gotInit, gotSteps, gotSidecars := convertScripts(images.ShellImage, images.ShellImageWin, []v1.Step{}, tc.sidecars, nil, SecurityContextConfig{SetSecurityContext: false, SetReadOnlyRootFilesystem: false}) gotInitScripts := "" if gotInit != nil { gotInitScripts = gotInit.Args[1] @@ -387,7 +387,7 @@ script-3`, Breakpoints: &v1.TaskBreakpoints{ OnFailure: "enabled", }, - }, true) + }, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}) wantInit := &corev1.Container{ Name: "place-scripts", @@ -457,7 +457,7 @@ fi debug-fail-continue-heredoc-randomly-generated-6nl7g `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount, debugScriptsVolumeMount}, - SecurityContext: LinuxSecurityContext, + SecurityContext: SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}.GetSecurityContext(false), } want := []corev1.Container{{ @@ -530,7 +530,7 @@ script-3`, Script: `#!/bin/sh sidecar-1`, Image: "sidecar-1", - }}, nil, true) + }}, nil, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImage, @@ -555,7 +555,7 @@ _EOF_ /tekton/bin/entrypoint decode-script "${scriptfile}" `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, - SecurityContext: LinuxSecurityContext, + SecurityContext: SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}.GetSecurityContext(false), } want := []corev1.Container{{ Image: "step-1", @@ -624,7 +624,7 @@ no-shebang`, Image: "step-3", VolumeMounts: preExistingVolumeMounts, Args: []string{"my", "args"}, - }}, []v1.Sidecar{}, nil, true) + }}, []v1.Sidecar{}, nil, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImageWin, @@ -706,7 +706,7 @@ script-3`, Script: `#!win pwsh -File sidecar-1`, Image: "sidecar-1", - }}, nil, true) + }}, nil, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImageWin, @@ -776,7 +776,7 @@ func TestConvertScripts_Windows_SidecarOnly(t *testing.T) { Script: `#!win python sidecar-1`, Image: "sidecar-1", - }}, nil, true) + }}, nil, SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}) wantInit := &corev1.Container{ Name: "place-scripts", Image: images.ShellImageWin, diff --git a/pkg/pod/security_context_config.go b/pkg/pod/security_context_config.go new file mode 100644 index 00000000000..bf91e4e4e5f --- /dev/null +++ b/pkg/pod/security_context_config.go @@ -0,0 +1,67 @@ +/* +Copyright 2024 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pod + +import ( + corev1 "k8s.io/api/core/v1" +) + +var ( + // Used in security context of pod init containers + allowPrivilegeEscalation = false + runAsNonRoot = true + readOnlyRootFilesystem = true + + // LinuxSecurityContext 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 adds securityContext that is supported by Windows OS. + WindowsSecurityContext = &corev1.SecurityContext{ + RunAsNonRoot: &runAsNonRoot, + } +) + +// SecurityContextConfig defines AffinityAssistant container configuration +type SecurityContextConfig struct { + SetSecurityContext bool + SetReadOnlyRootFilesystem bool +} + +func (c SecurityContextConfig) GetSecurityContext(isWindows bool) *corev1.SecurityContext { + if isWindows { + return WindowsSecurityContext + } + + if !c.SetReadOnlyRootFilesystem { + return LinuxSecurityContext + } + + securityContext := LinuxSecurityContext.DeepCopy() + securityContext.ReadOnlyRootFilesystem = &readOnlyRootFilesystem + return securityContext +} diff --git a/pkg/pod/workingdir_init.go b/pkg/pod/workingdir_init.go index 65da50b01e3..04c0f1121ef 100644 --- a/pkg/pod/workingdir_init.go +++ b/pkg/pod/workingdir_init.go @@ -35,7 +35,7 @@ import ( // allowing it to run in namespaces with restriced pod security admission. // If the init container will run on windows, `windows` should be set to `true`, // so that the correct security context can be applied. -func workingDirInit(workingdirinitImage string, stepContainers []corev1.Container, setSecurityContext, windows bool) *corev1.Container { +func workingDirInit(workingdirinitImage string, stepContainers []corev1.Container, securityContext SecurityContextConfig, windows bool) *corev1.Container { // Gather all unique workingDirs. workingDirs := sets.NewString() for _, step := range stepContainers { @@ -60,10 +60,6 @@ func workingDirInit(workingdirinitImage string, stepContainers []corev1.Containe // There are no workingDirs to initialize. return nil } - securityContext := LinuxSecurityContext - if windows { - securityContext = WindowsSecurityContext - } c := &corev1.Container{ Name: "working-dir-initializer", @@ -73,8 +69,9 @@ func workingDirInit(workingdirinitImage string, stepContainers []corev1.Containe WorkingDir: pipeline.WorkspaceDir, VolumeMounts: implicitVolumeMounts, } - if setSecurityContext { - c.SecurityContext = securityContext + if securityContext.SetSecurityContext { + c.SecurityContext = securityContext.GetSecurityContext(windows) } + return c } diff --git a/pkg/pod/workingdir_init_test.go b/pkg/pod/workingdir_init_test.go index c4a7b3b723d..710bf9234e9 100644 --- a/pkg/pod/workingdir_init_test.go +++ b/pkg/pod/workingdir_init_test.go @@ -29,11 +29,12 @@ import ( func TestWorkingDirInit(t *testing.T) { names.TestingSeed() for _, c := range []struct { - desc string - stepContainers []corev1.Container - windows bool - setSecurityContext bool - want *corev1.Container + desc string + stepContainers []corev1.Container + windows bool + setSecurityContext bool + setSecurityContextReadOnlyRoot bool + want *corev1.Container }{{ desc: "no workingDirs", stepContainers: []corev1.Container{{ @@ -82,7 +83,8 @@ func TestWorkingDirInit(t *testing.T) { // to /workspace, so we need to create it. WorkingDir: "/workspace/bbb", }}, - setSecurityContext: true, + setSecurityContext: true, + setSecurityContextReadOnlyRoot: true, want: &corev1.Container{ Name: "working-dir-initializer", Image: images.WorkingDirInitImage, @@ -90,7 +92,7 @@ func TestWorkingDirInit(t *testing.T) { Args: []string{"/workspace/bbb", "aaa", "zzz"}, WorkingDir: pipeline.WorkspaceDir, VolumeMounts: implicitVolumeMounts, - SecurityContext: LinuxSecurityContext, + SecurityContext: SecurityContextConfig{SetSecurityContext: true, SetReadOnlyRootFilesystem: true}.GetSecurityContext(false), }, }, { desc: "workingDirs are unique and sorted, absolute dirs are ignored, uses windows", @@ -135,8 +137,9 @@ func TestWorkingDirInit(t *testing.T) { // to /workspace, so we need to create it. WorkingDir: "/workspace/bbb", }}, - windows: true, - setSecurityContext: true, + windows: true, + setSecurityContext: true, + setSecurityContextReadOnlyRoot: true, want: &corev1.Container{ Name: "working-dir-initializer", Image: images.WorkingDirInitImage, @@ -148,7 +151,11 @@ func TestWorkingDirInit(t *testing.T) { }, }} { t.Run(c.desc, func(t *testing.T) { - got := workingDirInit(images.WorkingDirInitImage, c.stepContainers, c.setSecurityContext, c.windows) + securityContextConfig := SecurityContextConfig{ + SetSecurityContext: c.setSecurityContext, + SetReadOnlyRootFilesystem: c.setSecurityContextReadOnlyRoot, + } + got := workingDirInit(images.WorkingDirInitImage, c.stepContainers, securityContextConfig, c.windows) if d := cmp.Diff(c.want, got); d != "" { t.Fatalf("Diff %s", diff.PrintWantGot(d)) } diff --git a/pkg/reconciler/pipelinerun/affinity_assistant.go b/pkg/reconciler/pipelinerun/affinity_assistant.go index e88f4e54bf8..5cae8d297a8 100644 --- a/pkg/reconciler/pipelinerun/affinity_assistant.go +++ b/pkg/reconciler/pipelinerun/affinity_assistant.go @@ -23,8 +23,6 @@ import ( "errors" "fmt" - pipelinePod "github.com/tektoncd/pipeline/pkg/pod" - "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline" "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" @@ -142,12 +140,18 @@ func (c *Reconciler) createOrUpdateAffinityAssistant(ctx context.Context, affini if err != nil { return []error{err} } - affinityAssistantContainerConfig := aa.ContainerConfig{ - Image: c.Images.NopImage, - SetSecurityContext: cfg.FeatureFlags.SetSecurityContext, + + securityContextConfig := pipelinePod.SecurityContextConfig{ + SetSecurityContext: cfg.FeatureFlags.SetSecurityContext, + SetReadOnlyRootFilesystem: cfg.FeatureFlags.SetSecurityContextReadOnlyRootFilesystem, + } + + containerConfig := aa.ContainerConfig{ + Image: c.Images.NopImage, + SecurityContextConfig: securityContextConfig, } - affinityAssistantStatefulSet := affinityAssistantStatefulSet(aaBehavior, affinityAssistantName, pr, claimTemplates, claimNames, affinityAssistantContainerConfig, cfg.Defaults.DefaultAAPodTemplate) + affinityAssistantStatefulSet := affinityAssistantStatefulSet(aaBehavior, affinityAssistantName, pr, claimTemplates, claimNames, containerConfig, cfg.Defaults.DefaultAAPodTemplate) _, err = c.KubeClientSet.AppsV1().StatefulSets(pr.Namespace).Create(ctx, affinityAssistantStatefulSet, metav1.CreateOptions{}) if err != nil { errs = append(errs, fmt.Errorf("failed to create StatefulSet %s: %w", affinityAssistantName, err)) @@ -304,16 +308,7 @@ func affinityAssistantStatefulSet(aaBehavior aa.AffinityAssistantBehavior, name mounts = append(mounts, corev1.VolumeMount{Name: claimTemplate.Name, MountPath: claimTemplate.Name}) } - securityContext := &corev1.SecurityContext{} - if containerConfig.SetSecurityContext { - securityContext = pipelinePod.LinuxSecurityContext - - if tpl.NodeSelector[pipelinePod.OsSelectorLabel] == "windows" { - securityContext = pipelinePod.WindowsSecurityContext - } - } - - containers := []corev1.Container{{ + container := corev1.Container{ Name: "affinity-assistant", Image: containerConfig.Image, Args: []string{"tekton_run_indefinitely"}, @@ -331,9 +326,13 @@ func affinityAssistantStatefulSet(aaBehavior aa.AffinityAssistantBehavior, name "memory": resource.MustParse("100Mi"), }, }, - SecurityContext: securityContext, - VolumeMounts: mounts, - }} + VolumeMounts: mounts, + } + + if containerConfig.SecurityContextConfig.SetSecurityContext { + isWindows := tpl.NodeSelector[pipelinePod.OsSelectorLabel] == "windows" + container.SecurityContext = containerConfig.SecurityContextConfig.GetSecurityContext(isWindows) + } var volumes []corev1.Volume for i, claimName := range claimNames { @@ -375,7 +374,7 @@ func affinityAssistantStatefulSet(aaBehavior aa.AffinityAssistantBehavior, name Labels: getStatefulSetLabels(pr, name), }, Spec: corev1.PodSpec{ - Containers: containers, + Containers: []corev1.Container{container}, Tolerations: tpl.Tolerations, NodeSelector: tpl.NodeSelector, diff --git a/pkg/reconciler/pipelinerun/affinity_assistant_test.go b/pkg/reconciler/pipelinerun/affinity_assistant_test.go index 3b07469ea22..77ea501dcde 100644 --- a/pkg/reconciler/pipelinerun/affinity_assistant_test.go +++ b/pkg/reconciler/pipelinerun/affinity_assistant_test.go @@ -22,8 +22,6 @@ import ( "fmt" "testing" - pipelinePod "github.com/tektoncd/pipeline/pkg/pod" - "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/tektoncd/pipeline/pkg/apis/config" @@ -58,8 +56,16 @@ var ( podContainerFilter cmp.Option = cmpopts.IgnoreFields(corev1.Container{}, "Resources", "Args", "VolumeMounts") containerConfigWithoutSecurityContext = aa.ContainerConfig{ - Image: "nginx", - SetSecurityContext: false, + Image: "nginx", + SecurityContextConfig: pipelinePod.SecurityContextConfig{ + SetSecurityContext: false, + SetReadOnlyRootFilesystem: false, + }, + } + + securityContextConfigEnabledWithReadOnlyRootFilesystem = pipelinePod.SecurityContextConfig{ + SetSecurityContext: true, + SetReadOnlyRootFilesystem: true, } ) @@ -166,8 +172,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, Volumes: []corev1.Volume{{ Name: "workspace-0", @@ -193,8 +198,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, }, }, @@ -220,8 +224,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, Volumes: []corev1.Volume{{ Name: "workspace-0", @@ -247,14 +250,13 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, }, }, }, }, { - name: "securityContext feature enabled and os is Windows", + name: "set-security-context and set-security-context-read-only-root-filesystem feature enabled and os is Windows", pr: testPRWithWindowsOs, featureFlags: map[string]string{ "set-security-context": "true", @@ -279,10 +281,11 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { }, }, }, { - name: "securityContext feature enabled and os is Linux", + name: "set-security-context and set-security-context-read-only-root-filesystem feature enabled and os is Linux", pr: testPRWithEmptyDir, featureFlags: map[string]string{ - "set-security-context": "true", + "set-security-context": "true", + "set-security-context-read-only-root-filesystem": "true", }, expectStatefulSetSpec: &appsv1.StatefulSetSpec{ Replicas: &replicas, @@ -297,7 +300,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { Spec: corev1.PodSpec{ Containers: []corev1.Container{{ Name: "affinity-assistant", - SecurityContext: pipelinePod.LinuxSecurityContext, + SecurityContext: securityContextConfigEnabledWithReadOnlyRootFilesystem.GetSecurityContext(false), }}, }, }, @@ -369,8 +372,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, Volumes: []corev1.Volume{{ Name: "workspace-0", @@ -398,8 +400,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, Volumes: []corev1.Volume{{ Name: "workspace-0", @@ -432,8 +433,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, Volumes: []corev1.Volume{{ Name: "workspace-0", @@ -455,8 +455,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "affinity-assistant", - SecurityContext: &corev1.SecurityContext{}, + Name: "affinity-assistant", }}, Volumes: []corev1.Volume{{ Name: "workspace-0",