Kubernetes: Support allowPrivilegeEscalation and capabilities backend_options (#6307)

This commit is contained in:
Alex Caston
2026-04-28 17:17:00 +02:00
committed by GitHub
parent 2c801366ee
commit 43dcdc19a1
4 changed files with 138 additions and 31 deletions

View File

@@ -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:

View File

@@ -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 {
@@ -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"`

View File

@@ -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,
}
}
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 privileged {
securityContext := &kube_core_v1.SecurityContext{
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 {

View File

@@ -301,7 +301,11 @@ func TestFullPod(t *testing.T) {
],
"imagePullPolicy": "Always",
"securityContext": {
"privileged": true
"privileged": true,
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": ["ALL"]
}
}
}
],
@@ -379,12 +383,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",
@@ -527,6 +535,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) {