Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature flag set-security-context-read-only-root-filesystem to set readOnlyRootFilesystem for containers #8186

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/config-feature-flags.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 7 additions & 1 deletion docs/additional-configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -461,7 +466,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.

Expand Down
51 changes: 29 additions & 22 deletions pkg/apis/config/feature_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
43 changes: 22 additions & 21 deletions pkg/apis/config/feature_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 8 additions & 0 deletions pkg/internal/affinityassistant/affinityassistant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"context"
"fmt"

"github.com/tektoncd/pipeline/pkg/pod"

"github.com/tektoncd/pipeline/pkg/apis/config"
)

Expand Down Expand Up @@ -54,3 +56,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
SecurityContextConfig pod.SecurityContextConfig
}
66 changes: 23 additions & 43 deletions pkg/pod/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

// The following security contexts allow init containers to run in namespaces
// with "restricted" pod security admission
// See https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
linuxSecurityContext = &corev1.SecurityContext{
AllowPrivilegeEscalation: &allowPrivilegeEscalation,
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: &runAsNonRoot,
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
}
windowsSecurityContext = &corev1.SecurityContext{
RunAsNonRoot: &runAsNonRoot,
}
)

// Builder exposes options to configure Pod construction from TaskSpecs/Runs.
Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand All @@ -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)
}

Expand Down Expand Up @@ -695,18 +681,14 @@ 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}
for i, s := range steps {
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
Expand All @@ -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
}
Expand All @@ -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)
Expand Down Expand Up @@ -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
}

Expand All @@ -792,7 +772,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"
}

Expand Down
Loading