diff --git a/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md b/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md index 7e904effe..c15d99edd 100644 --- a/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md +++ b/docs/docs/30-administration/10-configuration/11-backends/20-kubernetes.md @@ -250,6 +250,15 @@ backend_options: localhostProfile: k8s-apparmor-example-deny-write ``` +or configure a specific `fsGroupChangePolicy` (Kubernetes defaults to 'Always') + +```yaml +backend_options: + kubernetes: + securityContext: + fsGroupChangePolicy: OnRootMismatch +``` + :::note The feature requires Kubernetes v1.30 or above. ::: diff --git a/pipeline/backend/kubernetes/backend_options.go b/pipeline/backend/kubernetes/backend_options.go index 4dbc2c14a..3b228fea3 100644 --- a/pipeline/backend/kubernetes/backend_options.go +++ b/pipeline/backend/kubernetes/backend_options.go @@ -2,6 +2,7 @@ package kubernetes import ( "github.com/go-viper/mapstructure/v2" + v1 "k8s.io/api/core/v1" backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) @@ -50,13 +51,14 @@ const ( ) type SecurityContext struct { - Privileged *bool `mapstructure:"privileged"` - RunAsNonRoot *bool `mapstructure:"runAsNonRoot"` - RunAsUser *int64 `mapstructure:"runAsUser"` - RunAsGroup *int64 `mapstructure:"runAsGroup"` - FSGroup *int64 `mapstructure:"fsGroup"` - 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 *v1.PodFSGroupChangePolicy `mapstructure:"fsGroupChangePolicy"` + SeccompProfile *SecProfile `mapstructure:"seccompProfile"` + ApparmorProfile *SecProfile `mapstructure:"apparmorProfile"` } type SecProfile struct { diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 912967547..ebabf1f31 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -462,12 +462,13 @@ func toleration(backendToleration Toleration) v1.Toleration { func podSecurityContext(sc *SecurityContext, secCtxConf SecurityContextConfig, stepPrivileged bool) *v1.PodSecurityContext { var ( - nonRoot *bool - user *int64 - group *int64 - fsGroup *int64 - seccomp *v1.SeccompProfile - apparmor *v1.AppArmorProfile + nonRoot *bool + user *int64 + group *int64 + fsGroup *int64 + fsGroupChangePolicy *v1.PodFSGroupChangePolicy + seccomp *v1.SeccompProfile + apparmor *v1.AppArmorProfile ) if secCtxConf.RunAsNonRoot { @@ -505,6 +506,7 @@ func podSecurityContext(sc *SecurityContext, secCtxConf SecurityContextConfig, s seccomp = seccompProfile(sc.SeccompProfile) apparmor = apparmorProfile(sc.ApparmorProfile) + fsGroupChangePolicy = sc.FsGroupChangePolicy } if nonRoot == nil && user == nil && group == nil && fsGroup == nil && seccomp == nil && apparmor == nil { @@ -512,12 +514,13 @@ func podSecurityContext(sc *SecurityContext, secCtxConf SecurityContextConfig, s } securityContext := &v1.PodSecurityContext{ - RunAsNonRoot: nonRoot, - RunAsUser: user, - RunAsGroup: group, - FSGroup: fsGroup, - SeccompProfile: seccomp, - AppArmorProfile: apparmor, + RunAsNonRoot: nonRoot, + RunAsUser: user, + RunAsGroup: group, + FSGroup: fsGroup, + FSGroupChangePolicy: fsGroupChangePolicy, + SeccompProfile: seccomp, + AppArmorProfile: apparmor, } log.Trace().Msgf("pod security context that will be used: %v", securityContext) return securityContext diff --git a/pipeline/backend/kubernetes/pod_test.go b/pipeline/backend/kubernetes/pod_test.go index 4b97c1e76..515d755ec 100644 --- a/pipeline/backend/kubernetes/pod_test.go +++ b/pipeline/backend/kubernetes/pod_test.go @@ -293,6 +293,7 @@ func TestFullPod(t *testing.T) { "runAsGroup": 101, "runAsNonRoot": true, "fsGroup": 101, + "fsGroupChangePolicy": "OnRootMismatch", "appArmorProfile": { "type": "Localhost", "localhostProfile": "k8s-apparmor-example-deny-write" @@ -348,12 +349,14 @@ func TestFullPod(t *testing.T) { {Number: 2345, Protocol: "tcp"}, {Number: 3456, Protocol: "udp"}, } + fsGroupChangePolicy := v1.PodFSGroupChangePolicy("OnRootMismatch") secCtx := SecurityContext{ - Privileged: newBool(true), - RunAsNonRoot: newBool(true), - RunAsUser: newInt64(101), - RunAsGroup: newInt64(101), - FSGroup: newInt64(101), + Privileged: newBool(true), + RunAsNonRoot: newBool(true), + RunAsUser: newInt64(101), + RunAsGroup: newInt64(101), + FSGroup: newInt64(101), + FsGroupChangePolicy: &fsGroupChangePolicy, SeccompProfile: &SecProfile{ Type: "Localhost", LocalhostProfile: "profiles/audit.json",