Skip to content

Commit

Permalink
Add set-security-context-read-only-root-filesystem as a feature flag
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kristofferchr committed Aug 7, 2024
1 parent bb18f3b commit f0fecb9
Show file tree
Hide file tree
Showing 15 changed files with 308 additions and 228 deletions.
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
5 changes: 5 additions & 0 deletions 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
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
6 changes: 4 additions & 2 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 @@ -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
}
60 changes: 20 additions & 40 deletions pkg/pod/pod.go
Original file line number Diff line number Diff line change
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

// 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.
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 Down
Loading

0 comments on commit f0fecb9

Please sign in to comment.