mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-05-04 12:32:36 +00:00
Kubernetes: Support allowPrivilegeEscalation and capabilities backend_options (#6307)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user