Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,26 @@ backend_options:
The feature requires Kubernetes v1.30 or above.
:::

You can set `allowPrivilegeEscalation` to `false` to prevent a container from gaining more privileges than its parent process.

```yaml
backend_options:
kubernetes:
securityContext:
allowPrivilegeEscalation: false
```

You can also drop [Linux capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) from a container. Adding capabilities is not allowed.

```yaml
backend_options:
kubernetes:
securityContext:
capabilities:
drop:
- ALL
```

### Annotations and labels

You can specify arbitrary [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) and [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) to be set on the Pod definition for a given workflow step using the following configuration:
Expand Down
22 changes: 14 additions & 8 deletions pipeline/backend/kubernetes/backend_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,16 @@ const (
)

type SecurityContext struct {
Privileged *bool `mapstructure:"privileged"`
RunAsNonRoot *bool `mapstructure:"runAsNonRoot"`
RunAsUser *int64 `mapstructure:"runAsUser"`
RunAsGroup *int64 `mapstructure:"runAsGroup"`
FSGroup *int64 `mapstructure:"fsGroup"`
FsGroupChangePolicy *kube_core_v1.PodFSGroupChangePolicy `mapstructure:"fsGroupChangePolicy"`
SeccompProfile *SecProfile `mapstructure:"seccompProfile"`
ApparmorProfile *SecProfile `mapstructure:"apparmorProfile"`
Privileged *bool `mapstructure:"privileged"`
RunAsNonRoot *bool `mapstructure:"runAsNonRoot"`
RunAsUser *int64 `mapstructure:"runAsUser"`
RunAsGroup *int64 `mapstructure:"runAsGroup"`
FSGroup *int64 `mapstructure:"fsGroup"`
FsGroupChangePolicy *kube_core_v1.PodFSGroupChangePolicy `mapstructure:"fsGroupChangePolicy"`
SeccompProfile *SecProfile `mapstructure:"seccompProfile"`
ApparmorProfile *SecProfile `mapstructure:"apparmorProfile"`
AllowPrivilegeEscalation *bool `mapstructure:"allowPrivilegeEscalation"`
Capabilities *Capabilities `mapstructure:"capabilities"`
}

type SecProfile struct {
Expand All @@ -83,6 +85,10 @@ type SecProfile struct {

type SecProfileType string

type Capabilities struct {
Drop []string `mapstructure:"drop"`
}

// SecretRef defines Kubernetes secret reference.
type SecretRef struct {
Name string `mapstructure:"name"`
Expand Down
56 changes: 40 additions & 16 deletions pipeline/backend/kubernetes/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,33 +590,57 @@ func apparmorProfile(scp *SecProfile) *kube_core_v1.AppArmorProfile {
return apparmorProfile
}

func containerSecurityContext(sc *SecurityContext, stepPrivileged bool) *kube_core_v1.SecurityContext {
if !stepPrivileged {
func containerCapabilities(capabilities *Capabilities) *kube_core_v1.Capabilities {
if capabilities == nil || len(capabilities.Drop) == 0 {
return nil
}

//nolint:staticcheck
privileged := false
drop := make([]kube_core_v1.Capability, len(capabilities.Drop))

// if security context privileged is set explicitly
if sc != nil && sc.Privileged != nil && *sc.Privileged {
privileged = true
for i, c := range capabilities.Drop {
drop[i] = kube_core_v1.Capability(c)
}

// if security context privileged is not set explicitly, but step is privileged
if (sc == nil || sc.Privileged == nil) && stepPrivileged {
privileged = true
return &kube_core_v1.Capabilities{
Drop: drop,
}
}

if privileged {
securityContext := &kube_core_v1.SecurityContext{
Privileged: newBool(true),
func containerSecurityContext(sc *SecurityContext, stepPrivileged bool) *kube_core_v1.SecurityContext {
var (
privileged *bool
allowPrivilegeEscalation *bool
capabilities *kube_core_v1.Capabilities
)

// A container may only run privileged when the step itself is privileged.
// If the step is privileged, the container is privileged by default unless
// explicitly disabled via securityContext.privileged=false.
if stepPrivileged && (sc == nil || sc.Privileged == nil || *sc.Privileged) {
privileged = newBool(true)
}

if sc != nil {
// allowPrivilegeEscalation can only be set to false.
if sc.AllowPrivilegeEscalation != nil && !*sc.AllowPrivilegeEscalation {
allowPrivilegeEscalation = sc.AllowPrivilegeEscalation
}
log.Trace().Msgf("container security context that will be used: %v", securityContext)
return securityContext

capabilities = containerCapabilities(sc.Capabilities)
}

return nil
if privileged == nil && capabilities == nil && allowPrivilegeEscalation == nil {
return nil
}

securityContext := &kube_core_v1.SecurityContext{
Privileged: privileged,
AllowPrivilegeEscalation: allowPrivilegeEscalation,
Capabilities: capabilities,
}

log.Trace().Msgf("container security context that will be used: %v", securityContext)
return securityContext
}

func mapToEnvVars(m map[string]string) []kube_core_v1.EnvVar {
Expand Down
71 changes: 64 additions & 7 deletions pipeline/backend/kubernetes/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,11 @@ func TestFullPod(t *testing.T) {
],
"imagePullPolicy": "Always",
"securityContext": {
"privileged": true
"privileged": true,
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": ["ALL"]
}
}
}
],
Expand Down Expand Up @@ -378,12 +382,16 @@ func TestFullPod(t *testing.T) {
}
fsGroupChangePolicy := kube_core_v1.PodFSGroupChangePolicy("OnRootMismatch")
secCtx := SecurityContext{
Privileged: newBool(true),
RunAsNonRoot: newBool(true),
RunAsUser: newInt64(101),
RunAsGroup: newInt64(101),
FSGroup: newInt64(101),
FsGroupChangePolicy: &fsGroupChangePolicy,
Privileged: newBool(true),
RunAsNonRoot: newBool(true),
RunAsUser: newInt64(101),
RunAsGroup: newInt64(101),
FSGroup: newInt64(101),
FsGroupChangePolicy: &fsGroupChangePolicy,
AllowPrivilegeEscalation: newBool(false),
Capabilities: &Capabilities{
Drop: []string{"ALL"},
},
SeccompProfile: &SecProfile{
Type: "Localhost",
LocalhostProfile: "profiles/audit.json",
Expand Down Expand Up @@ -526,6 +534,55 @@ func TestPodPrivilege(t *testing.T) {
pod, err = createTestPod(false, true, secCtx)
assert.NoError(t, err)
assert.True(t, *pod.Spec.SecurityContext.RunAsNonRoot)

// non-privileged step with allowPrivilegeEscalation=false: applied
secCtx = SecurityContext{
AllowPrivilegeEscalation: newBool(false),
}
pod, err = createTestPod(false, false, secCtx)
assert.NoError(t, err)
assert.NotNil(t, pod.Spec.Containers[0].SecurityContext)
assert.False(t, *pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.Privileged)

// non-privileged step with allowPrivilegeEscalation=true: ignored
secCtx = SecurityContext{
AllowPrivilegeEscalation: newBool(true),
}
pod, err = createTestPod(false, false, secCtx)
assert.NoError(t, err)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext)

// privileged step with allowPrivilegeEscalation=true: ignored
secCtx = SecurityContext{
AllowPrivilegeEscalation: newBool(true),
}
pod, err = createTestPod(true, false, secCtx)
assert.NoError(t, err)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation)

// non-privileged step with capabilities drop: applied
secCtx = SecurityContext{
Capabilities: &Capabilities{Drop: []string{"ALL"}},
}
pod, err = createTestPod(false, false, secCtx)
assert.NoError(t, err)
assert.NotNil(t, pod.Spec.Containers[0].SecurityContext)
assert.Equal(t, []kube_core_v1.Capability{"ALL"}, pod.Spec.Containers[0].SecurityContext.Capabilities.Drop)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.Capabilities.Add)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.Privileged)

// non-privileged step with drop capabilities and allowPrivilegeEscalation=false: both applied
secCtx = SecurityContext{
AllowPrivilegeEscalation: newBool(false),
Capabilities: &Capabilities{Drop: []string{"ALL"}},
}
pod, err = createTestPod(false, false, secCtx)
assert.NoError(t, err)
assert.NotNil(t, pod.Spec.Containers[0].SecurityContext)
assert.Nil(t, pod.Spec.Containers[0].SecurityContext.Privileged)
assert.False(t, *pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation)
assert.Equal(t, []kube_core_v1.Capability{"ALL"}, pod.Spec.Containers[0].SecurityContext.Capabilities.Drop)
}

func TestScratchPod(t *testing.T) {
Expand Down