From bb18f3be989d602fd88faca063e1c4d3ec2ba651 Mon Sep 17 00:00:00 2001 From: Kristoffer Moberg Christensen Date: Mon, 5 Aug 2024 15:29:12 +0200 Subject: [PATCH 1/2] Implement set-security-context feature for affinity assistant containers Ensures that when using Affinity Assistant, one can adhere to restricted pod security standards. Enables users to apply a container level securityContext for Affinity Assistants. --- docs/additional-configs.md | 3 +- .../affinityassistant_types.go | 6 + pkg/pod/pod.go | 20 +-- pkg/pod/pod_test.go | 12 +- pkg/pod/script.go | 4 +- pkg/pod/script_test.go | 12 +- pkg/pod/workingdir_init.go | 4 +- pkg/pod/workingdir_init_test.go | 4 +- .../pipelinerun/affinity_assistant.go | 26 +++- .../pipelinerun/affinity_assistant_test.go | 140 ++++++++++++++++-- 10 files changed, 188 insertions(+), 43 deletions(-) diff --git a/docs/additional-configs.md b/docs/additional-configs.md index 159fa5506b4..e71f09123e4 100644 --- a/docs/additional-configs.md +++ b/docs/additional-configs.md @@ -461,7 +461,8 @@ Out-of-the-box, Tekton Pipelines Controller is configured for relatively small-s 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). +to any containers injected into TaskRuns by the Pipelines controller. If the [Affinity Assistants](affinityassistants.md) feature is enabled, the SecurityContext is also applied to those containers. +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. diff --git a/pkg/internal/affinityassistant/affinityassistant_types.go b/pkg/internal/affinityassistant/affinityassistant_types.go index e4fddb4e23d..15afe705b98 100644 --- a/pkg/internal/affinityassistant/affinityassistant_types.go +++ b/pkg/internal/affinityassistant/affinityassistant_types.go @@ -54,3 +54,9 @@ func GetAffinityAssistantBehavior(ctx context.Context) (AffinityAssistantBehavio return "", fmt.Errorf("unknown combination of disable-affinity-assistant: %v and coschedule: %v", disableAA, coschedule) } + +// ContainerConfig defines AffinityAssistant container configuration +type ContainerConfig struct { + Image string + SetSecurityContext bool +} diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go index fc0db922d8a..31cc2fb9081 100644 --- a/pkg/pod/pod.go +++ b/pkg/pod/pod.go @@ -60,8 +60,8 @@ 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" + // 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" // TerminationReasonTimeoutExceeded indicates a step execution timed out. TerminationReasonTimeoutExceeded = "TimeoutExceeded" @@ -132,10 +132,10 @@ var ( allowPrivilegeEscalation = false runAsNonRoot = true - // The following security contexts allow init containers to run in namespaces + // 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{ + LinuxSecurityContext = &corev1.SecurityContext{ AllowPrivilegeEscalation: &allowPrivilegeEscalation, Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, @@ -145,7 +145,7 @@ var ( Type: corev1.SeccompProfileTypeRuntimeDefault, }, } - windowsSecurityContext = &corev1.SecurityContext{ + WindowsSecurityContext = &corev1.SecurityContext{ RunAsNonRoot: &runAsNonRoot, } ) @@ -703,9 +703,9 @@ func entrypointInitContainer(image string, steps []v1.Step, setSecurityContext, command = append(command, StepName(s.Name, i)) } volumeMounts := []corev1.VolumeMount{binMount, internalStepsMount} - securityContext := linuxSecurityContext + securityContext := LinuxSecurityContext if windows { - securityContext = windowsSecurityContext + securityContext = WindowsSecurityContext } // Rewrite steps with entrypoint binary. Append the entrypoint init @@ -775,9 +775,9 @@ func createResultsSidecar(taskSpec v1.TaskSpec, image string, setSecurityContext Image: image, Command: command, } - securityContext := linuxSecurityContext + securityContext := LinuxSecurityContext if windows { - securityContext = windowsSecurityContext + securityContext = WindowsSecurityContext } if setSecurityContext { sidecar.SecurityContext = securityContext @@ -792,7 +792,7 @@ func usesWindows(tr *v1.TaskRun) bool { if tr.Spec.PodTemplate == nil || tr.Spec.PodTemplate.NodeSelector == nil { return false } - osSelector := tr.Spec.PodTemplate.NodeSelector[osSelectorLabel] + osSelector := tr.Spec.PodTemplate.NodeSelector[OsSelectorLabel] return osSelector == "windows" } diff --git a/pkg/pod/pod_test.go b/pkg/pod/pod_test.go index 8eb65ae5e60..da6a32644e7 100644 --- a/pkg/pod/pod_test.go +++ b/pkg/pod/pod_test.go @@ -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: LinuxSecurityContext, }}, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -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: LinuxSecurityContext, }}, Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{ Name: "tekton-creds-init-home-0", @@ -3420,7 +3420,7 @@ func TestPrepareInitContainers(t *testing.T) { WorkingDir: "/", Command: []string{"/ko-app/entrypoint", "init", "/ko-app/entrypoint", entrypointBinary, "step-foo", "step-bar"}, VolumeMounts: []corev1.VolumeMount{binMount, internalStepsMount}, - SecurityContext: linuxSecurityContext, + SecurityContext: LinuxSecurityContext, }, }, { name: "nothing-special-two-steps-windows", @@ -3452,7 +3452,7 @@ func TestPrepareInitContainers(t *testing.T) { WorkingDir: "/", Command: []string{"/ko-app/entrypoint", "init", "/ko-app/entrypoint", entrypointBinary, "step-foo", "step-bar"}, VolumeMounts: []corev1.VolumeMount{binMount, internalStepsMount}, - SecurityContext: windowsSecurityContext, + SecurityContext: WindowsSecurityContext, }, }} for _, tc := range tcs { @@ -3481,13 +3481,13 @@ func TestUsesWindows(t *testing.T) { }, { name: "uses linux", taskRun: &v1.TaskRun{Spec: v1.TaskRunSpec{PodTemplate: &pod.Template{NodeSelector: map[string]string{ - osSelectorLabel: "linux", + OsSelectorLabel: "linux", }}}}, want: false, }, { name: "uses windows", taskRun: &v1.TaskRun{Spec: v1.TaskRunSpec{PodTemplate: &pod.Template{NodeSelector: map[string]string{ - osSelectorLabel: "windows", + OsSelectorLabel: "windows", }}}}, want: true, }} diff --git a/pkg/pod/script.go b/pkg/pod/script.go index 6b0af47467c..efcbc283858 100644 --- a/pkg/pod/script.go +++ b/pkg/pod/script.go @@ -87,13 +87,13 @@ func convertScripts(shellImageLinux string, shellImageWin string, steps []v1.Ste shellImage := shellImageLinux shellCommand := "sh" shellArg := "-c" - securityContext := linuxSecurityContext + securityContext := LinuxSecurityContext // Set windows variants for Image, Command and Args if requiresWindows { shellImage = shellImageWin shellCommand = "pwsh" shellArg = "-Command" - securityContext = windowsSecurityContext + securityContext = WindowsSecurityContext } placeScriptsInit := corev1.Container{ diff --git a/pkg/pod/script_test.go b/pkg/pod/script_test.go index 93c1ed51ce5..b8ce120676e 100644 --- a/pkg/pod/script_test.go +++ b/pkg/pod/script_test.go @@ -159,7 +159,7 @@ _EOF_ /tekton/bin/entrypoint decode-script "${scriptfile}" `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, - SecurityContext: linuxSecurityContext, + SecurityContext: LinuxSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -457,7 +457,7 @@ fi debug-fail-continue-heredoc-randomly-generated-6nl7g `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount, debugScriptsVolumeMount}, - SecurityContext: linuxSecurityContext, + SecurityContext: LinuxSecurityContext, } want := []corev1.Container{{ @@ -555,7 +555,7 @@ _EOF_ /tekton/bin/entrypoint decode-script "${scriptfile}" `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, - SecurityContext: linuxSecurityContext, + SecurityContext: LinuxSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -642,7 +642,7 @@ no-shebang "@ | Out-File -FilePath /tekton/scripts/script-3-mssqb.cmd `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, - SecurityContext: windowsSecurityContext, + SecurityContext: WindowsSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -725,7 +725,7 @@ sidecar-1 "@ | Out-File -FilePath /tekton/scripts/sidecar-script-0-mssqb `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, - SecurityContext: windowsSecurityContext, + SecurityContext: WindowsSecurityContext, } want := []corev1.Container{{ Image: "step-1", @@ -787,7 +787,7 @@ sidecar-1 "@ | Out-File -FilePath /tekton/scripts/sidecar-script-0-9l9zj `}, VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, binMount}, - SecurityContext: windowsSecurityContext, + SecurityContext: WindowsSecurityContext, } want := []corev1.Container{{ Image: "step-1", diff --git a/pkg/pod/workingdir_init.go b/pkg/pod/workingdir_init.go index af001dc104d..65da50b01e3 100644 --- a/pkg/pod/workingdir_init.go +++ b/pkg/pod/workingdir_init.go @@ -60,9 +60,9 @@ func workingDirInit(workingdirinitImage string, stepContainers []corev1.Containe // There are no workingDirs to initialize. return nil } - securityContext := linuxSecurityContext + securityContext := LinuxSecurityContext if windows { - securityContext = windowsSecurityContext + securityContext = WindowsSecurityContext } c := &corev1.Container{ diff --git a/pkg/pod/workingdir_init_test.go b/pkg/pod/workingdir_init_test.go index 05f1f65f133..c4a7b3b723d 100644 --- a/pkg/pod/workingdir_init_test.go +++ b/pkg/pod/workingdir_init_test.go @@ -90,7 +90,7 @@ func TestWorkingDirInit(t *testing.T) { Args: []string{"/workspace/bbb", "aaa", "zzz"}, WorkingDir: pipeline.WorkspaceDir, VolumeMounts: implicitVolumeMounts, - SecurityContext: linuxSecurityContext, + SecurityContext: LinuxSecurityContext, }, }, { desc: "workingDirs are unique and sorted, absolute dirs are ignored, uses windows", @@ -144,7 +144,7 @@ func TestWorkingDirInit(t *testing.T) { Args: []string{"/workspace/bbb", "aaa", "zzz"}, WorkingDir: pipeline.WorkspaceDir, VolumeMounts: implicitVolumeMounts, - SecurityContext: windowsSecurityContext, + SecurityContext: WindowsSecurityContext, }, }} { t.Run(c.desc, func(t *testing.T) { diff --git a/pkg/reconciler/pipelinerun/affinity_assistant.go b/pkg/reconciler/pipelinerun/affinity_assistant.go index 87ff43fe3b8..e88f4e54bf8 100644 --- a/pkg/reconciler/pipelinerun/affinity_assistant.go +++ b/pkg/reconciler/pipelinerun/affinity_assistant.go @@ -23,12 +23,15 @@ 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" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/internal/affinityassistant" aa "github.com/tektoncd/pipeline/pkg/internal/affinityassistant" + pipelinePod "github.com/tektoncd/pipeline/pkg/pod" "github.com/tektoncd/pipeline/pkg/reconciler/volumeclaim" "github.com/tektoncd/pipeline/pkg/workspace" appsv1 "k8s.io/api/apps/v1" @@ -139,7 +142,12 @@ func (c *Reconciler) createOrUpdateAffinityAssistant(ctx context.Context, affini if err != nil { return []error{err} } - affinityAssistantStatefulSet := affinityAssistantStatefulSet(aaBehavior, affinityAssistantName, pr, claimTemplates, claimNames, c.Images.NopImage, cfg.Defaults.DefaultAAPodTemplate) + affinityAssistantContainerConfig := aa.ContainerConfig{ + Image: c.Images.NopImage, + SetSecurityContext: cfg.FeatureFlags.SetSecurityContext, + } + + affinityAssistantStatefulSet := affinityAssistantStatefulSet(aaBehavior, affinityAssistantName, pr, claimTemplates, claimNames, affinityAssistantContainerConfig, 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)) @@ -281,7 +289,7 @@ func getStatefulSetLabels(pr *v1.PipelineRun, affinityAssistantName string) map[ // with the given AffinityAssistantTemplate applied to the StatefulSet PodTemplateSpec. // The VolumeClaimTemplates and Volume of StatefulSet reference the PipelineRun WorkspaceBinding VolumeClaimTempalte and the PVCs respectively. // The PVs created by the StatefulSet are scheduled to the same availability zone which avoids PV scheduling conflict. -func affinityAssistantStatefulSet(aaBehavior aa.AffinityAssistantBehavior, name string, pr *v1.PipelineRun, claimTemplates []corev1.PersistentVolumeClaim, claimNames []string, affinityAssistantImage string, defaultAATpl *pod.AffinityAssistantTemplate) *appsv1.StatefulSet { +func affinityAssistantStatefulSet(aaBehavior aa.AffinityAssistantBehavior, name string, pr *v1.PipelineRun, claimTemplates []corev1.PersistentVolumeClaim, claimNames []string, containerConfig aa.ContainerConfig, defaultAATpl *pod.AffinityAssistantTemplate) *appsv1.StatefulSet { // We want a singleton pod replicas := int32(1) @@ -296,9 +304,18 @@ 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{{ Name: "affinity-assistant", - Image: affinityAssistantImage, + Image: containerConfig.Image, Args: []string{"tekton_run_indefinitely"}, // Set requests == limits to get QoS class _Guaranteed_. @@ -314,7 +331,8 @@ func affinityAssistantStatefulSet(aaBehavior aa.AffinityAssistantBehavior, name "memory": resource.MustParse("100Mi"), }, }, - VolumeMounts: mounts, + SecurityContext: securityContext, + VolumeMounts: mounts, }} var volumes []corev1.Volume diff --git a/pkg/reconciler/pipelinerun/affinity_assistant_test.go b/pkg/reconciler/pipelinerun/affinity_assistant_test.go index 15c721899a8..3b07469ea22 100644 --- a/pkg/reconciler/pipelinerun/affinity_assistant_test.go +++ b/pkg/reconciler/pipelinerun/affinity_assistant_test.go @@ -22,6 +22,8 @@ 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" @@ -30,6 +32,7 @@ import ( "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" aa "github.com/tektoncd/pipeline/pkg/internal/affinityassistant" + pipelinePod "github.com/tektoncd/pipeline/pkg/pod" "github.com/tektoncd/pipeline/pkg/reconciler/volumeclaim" "github.com/tektoncd/pipeline/pkg/workspace" "github.com/tektoncd/pipeline/test/diff" @@ -50,8 +53,14 @@ import ( ) var ( - podSpecFilter cmp.Option = cmpopts.IgnoreFields(corev1.PodSpec{}, "Containers", "Affinity") + podSpecFilter cmp.Option = cmpopts.IgnoreFields(corev1.PodSpec{}, "Affinity") podTemplateSpecFilter cmp.Option = cmpopts.IgnoreFields(corev1.PodTemplateSpec{}, "ObjectMeta") + podContainerFilter cmp.Option = cmpopts.IgnoreFields(corev1.Container{}, "Resources", "Args", "VolumeMounts") + + containerConfigWithoutSecurityContext = aa.ContainerConfig{ + Image: "nginx", + SetSecurityContext: false, + } ) var ( @@ -117,14 +126,31 @@ var testPRWithEmptyDir = &v1.PipelineRun{ }, } +var testPRWithWindowsOs = &v1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "pipelinerun-with-windows"}, + Spec: v1.PipelineRunSpec{ + TaskRunTemplate: v1.PipelineTaskRunTemplate{ + PodTemplate: &pod.PodTemplate{ + NodeSelector: map[string]string{pipelinePod.OsSelectorLabel: "windows"}, + }, + }, + Workspaces: []v1.WorkspaceBinding{{ + Name: "EmptyDir Workspace", + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }}, + }, +} + // TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun tests to create and delete Affinity Assistants and PVCs // per pipelinerun for a given PipelineRun func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { replicas := int32(1) + tests := []struct { name string pr *v1.PipelineRun expectStatefulSetSpec *appsv1.StatefulSetSpec + featureFlags map[string]string }{{ name: "PersistentVolumeClaim Workspace type", pr: testPRWithPVC, @@ -139,6 +165,10 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { }, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, Volumes: []corev1.Volume{{ Name: "workspace-0", VolumeSource: corev1.VolumeSource{ @@ -160,6 +190,14 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { workspace.LabelComponent: workspace.ComponentNameAffinityAssistant, }, }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, + }, + }, VolumeClaimTemplates: []corev1.PersistentVolumeClaim{{ ObjectMeta: metav1.ObjectMeta{Name: "pvc-b9eea16dce"}, }}, @@ -181,6 +219,10 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { }}, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, Volumes: []corev1.Volume{{ Name: "workspace-0", VolumeSource: corev1.VolumeSource{ @@ -202,17 +244,79 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerPipelineRun(t *testing.T) { workspace.LabelComponent: workspace.ComponentNameAffinityAssistant, }, }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, + }, + }, + }, + }, { + name: "securityContext feature enabled and os is Windows", + pr: testPRWithWindowsOs, + featureFlags: map[string]string{ + "set-security-context": "true", + }, + expectStatefulSetSpec: &appsv1.StatefulSetSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + pipeline.PipelineRunLabelKey: testPRWithWindowsOs.Name, + workspace.LabelInstance: "affinity-assistant-01cecfbdec", + workspace.LabelComponent: workspace.ComponentNameAffinityAssistant, + }, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + NodeSelector: map[string]string{pipelinePod.OsSelectorLabel: "windows"}, + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: pipelinePod.WindowsSecurityContext, + }}, + }, + }, + }, + }, { + name: "securityContext feature enabled and os is Linux", + pr: testPRWithEmptyDir, + featureFlags: map[string]string{ + "set-security-context": "true", + }, + expectStatefulSetSpec: &appsv1.StatefulSetSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + pipeline.PipelineRunLabelKey: testPRWithEmptyDir.Name, + workspace.LabelInstance: "affinity-assistant-c655a0c8a2", + workspace.LabelComponent: workspace.ComponentNameAffinityAssistant, + }, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: pipelinePod.LinuxSecurityContext, + }}, + }, + }, }, }} for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - configMap := map[string]string{ + featureFlags := map[string]string{ "disable-affinity-assistant": "true", "coschedule": "pipelineruns", } + + for k, v := range tc.featureFlags { + featureFlags[k] = v + } + kubeClientSet := fakek8s.NewSimpleClientset() - ctx := cfgtesting.SetFeatureFlags(context.Background(), t, configMap) + ctx := cfgtesting.SetFeatureFlags(context.Background(), t, featureFlags) c := Reconciler{ KubeClientSet: kubeClientSet, pvcHandler: volumeclaim.NewPVCHandler(kubeClientSet, zap.NewExample().Sugar()), @@ -264,6 +368,10 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin }, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, Volumes: []corev1.Volume{{ Name: "workspace-0", VolumeSource: corev1.VolumeSource{ @@ -289,6 +397,10 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin }, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, Volumes: []corev1.Volume{{ Name: "workspace-0", VolumeSource: corev1.VolumeSource{ @@ -319,6 +431,10 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin }, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, Volumes: []corev1.Volume{{ Name: "workspace-0", VolumeSource: corev1.VolumeSource{ @@ -338,6 +454,10 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCsPerWorkspaceOrDisabled(t *testin }, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "affinity-assistant", + SecurityContext: &corev1.SecurityContext{}, + }}, Volumes: []corev1.Volume{{ Name: "workspace-0", VolumeSource: corev1.VolumeSource{ @@ -475,7 +595,7 @@ func TestCreateOrUpdateAffinityAssistantsAndPVCs_Failure(t *testing.T) { } // TestCreateOrUpdateAffinityAssistantWhenNodeIsCordoned tests an existing Affinity Assistant can identify the node failure and -// can migrate the affinity assistant pod to a healthy node so that the existing pipelineRun runs to competition +// can migrate the affinity assistant pod to a healthy node so that the existing pipelineRun runs to compleition func TestCreateOrUpdateAffinityAssistantWhenNodeIsCordoned(t *testing.T) { expectedAffinityAssistantName := GetAffinityAssistantName(workspacePVCName, testPRWithPVC.Name) @@ -621,7 +741,7 @@ func TestPipelineRunPodTemplatesArePropagatedToAffinityAssistant(t *testing.T) { }, } - stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, "nginx", nil) + stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, containerConfigWithoutSecurityContext, nil) if len(stsWithTolerationsAndNodeSelector.Spec.Template.Spec.Tolerations) != 1 { t.Errorf("expected Tolerations in the StatefulSet") @@ -659,7 +779,7 @@ func TestDefaultPodTemplatesArePropagatedToAffinityAssistant(t *testing.T) { }}, } - stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, "nginx", defaultTpl) + stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, containerConfigWithoutSecurityContext, defaultTpl) if len(stsWithTolerationsAndNodeSelector.Spec.Template.Spec.Tolerations) != 1 { t.Errorf("expected Tolerations in the StatefulSet") @@ -707,7 +827,7 @@ func TestMergedPodTemplatesArePropagatedToAffinityAssistant(t *testing.T) { }}, } - stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, "nginx", defaultTpl) + stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, containerConfigWithoutSecurityContext, defaultTpl) if len(stsWithTolerationsAndNodeSelector.Spec.Template.Spec.Tolerations) != 1 { t.Errorf("expected Tolerations from spec in the StatefulSet") @@ -746,7 +866,7 @@ func TestOnlySelectPodTemplateFieldsArePropagatedToAffinityAssistant(t *testing. }, } - stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, "nginx", nil) + stsWithTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, containerConfigWithoutSecurityContext, nil) if len(stsWithTolerationsAndNodeSelector.Spec.Template.Spec.Tolerations) != 1 { t.Errorf("expected Tolerations from spec in the StatefulSet") @@ -766,7 +886,7 @@ func TestThatTheAffinityAssistantIsWithoutNodeSelectorAndTolerations(t *testing. Spec: v1.PipelineRunSpec{}, } - stsWithoutTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithoutCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, "nginx", nil) + stsWithoutTolerationsAndNodeSelector := affinityAssistantStatefulSet(aa.AffinityAssistantPerWorkspace, "test-assistant", prWithoutCustomPodTemplate, []corev1.PersistentVolumeClaim{}, []string{}, containerConfigWithoutSecurityContext, nil) if len(stsWithoutTolerationsAndNodeSelector.Spec.Template.Spec.Tolerations) != 0 { t.Errorf("unexpected Tolerations in the StatefulSet") @@ -1244,7 +1364,7 @@ func validateStatefulSetSpec(t *testing.T, ctx context.Context, c Reconciler, ex if err != nil { t.Fatalf("unexpected error when retrieving StatefulSet: %v", err) } - if d := cmp.Diff(expectStatefulSetSpec, &aa.Spec, podSpecFilter, podTemplateSpecFilter); d != "" { + if d := cmp.Diff(expectStatefulSetSpec, &aa.Spec, podSpecFilter, podTemplateSpecFilter, podContainerFilter); d != "" { t.Errorf("StatefulSetSpec diff: %s", diff.PrintWantGot(d)) } } else if !apierrors.IsNotFound(err) { From f0fecb99edc4016d894f06c26e4c89e8644b72a5 Mon Sep 17 00:00:00 2001 From: Kristoffer Moberg Christensen Date: Sat, 22 Jun 2024 13:52:56 +0200 Subject: [PATCH 2/2] Add set-security-context-read-only-root-filesystem as a feature flag Prior to this change it was not possible to set readOnlyRootFilesystem for tekton pipeline containers. This change allows users to set readOnlyRootFilesystem in container securityContext for all containers. Aligns tekton with industry best practices for kuberenetes security, such as Azure Kubernetes Services deployment safeguards. --- config/config-feature-flags.yaml | 2 + docs/additional-configs.md | 5 + pkg/apis/config/feature_flags.go | 51 ++++--- pkg/apis/config/feature_flags_test.go | 43 +++--- .../testdata/feature-flags-all-flags-set.yaml | 1 + .../affinityassistant_types.go | 6 +- pkg/pod/pod.go | 60 +++----- pkg/pod/pod_test.go | 141 ++++++++++-------- pkg/pod/script.go | 10 +- pkg/pod/script_test.go | 26 ++-- pkg/pod/security_context_config.go | 67 +++++++++ pkg/pod/workingdir_init.go | 11 +- pkg/pod/workingdir_init_test.go | 27 ++-- .../pipelinerun/affinity_assistant.go | 39 +++-- .../pipelinerun/affinity_assistant_test.go | 47 +++--- 15 files changed, 308 insertions(+), 228 deletions(-) create mode 100644 pkg/pod/security_context_config.go 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",