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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 readOnlyRootFilesystem in securityContext for all containers used in TaskRuns and AffinityAssistant.
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
46 changes: 25 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 Expand Up @@ -318,6 +319,9 @@ func TestNewFeatureFlagsConfigMapErrors(t *testing.T) {
}, {
fileName: "feature-flags-invalid-enable-kubernetes-sidecar",
want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax`,
}, {
fileName: "feature-flags-invalid-set_security_context_read_only_root_filesystem",
want: `failed parsing feature flags config "invalid read only root filesystem flag": strconv.ParseBool: parsing "invalid read only root filesystem flag": invalid syntax`,
}} {
t.Run(tc.fileName, func(t *testing.T) {
cm := test.ConfigMapFromTestFile(t, tc.fileName)
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2021 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
#
# https://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.

apiVersion: v1
kind: ConfigMap
metadata:
name: feature-flags
namespace: tekton-pipelines
data:
set-security-context-read-only-root-filesystem: "invalid read only root filesystem flag"
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 @@ -128,27 +128,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 @@ -179,6 +158,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 @@ -213,11 +193,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 @@ -232,15 +218,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 @@ -251,7 +237,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 @@ -594,18 +580,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 @@ -620,8 +602,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 @@ -631,7 +613,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 @@ -674,13 +656,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
Loading