Skip to content

Commit

Permalink
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 28 deletions.
79 changes: 71 additions & 8 deletions pipeline/backend/kubernetes/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ func podName(step *types.Step) (string, error) {

func podMeta(step *types.Step, config *config, podName string) metav1.ObjectMeta {
meta := metav1.ObjectMeta{
Name: podName,
Namespace: config.Namespace,
Annotations: config.PodAnnotations,
Name: podName,
Namespace: config.Namespace,
}

labels := make(map[string]string, len(config.PodLabels)+1)
Expand All @@ -81,6 +80,18 @@ func podMeta(step *types.Step, config *config, podName string) metav1.ObjectMeta
labels[StepLabel] = step.Name
meta.Labels = labels

// copy to not alter the engine config
meta.Annotations = make(map[string]string, len(config.PodAnnotations))
maps.Copy(meta.Annotations, config.PodAnnotations)

securityContext := step.BackendOptions.Kubernetes.SecurityContext
if securityContext != nil {
key, value := apparmorAnnotation(podName, securityContext.ApparmorProfile)
if key != nil && value != nil {
meta.Annotations[*key] = *value
}
}

return meta
}

Expand Down Expand Up @@ -297,6 +308,7 @@ func podSecurityContext(sc *types.SecurityContext, secCtxConf SecurityContextCon
user *int64
group *int64
fsGroup *int64
seccomp *v1.SeccompProfile
)

if sc != nil && sc.RunAsNonRoot != nil {
Expand All @@ -313,20 +325,41 @@ func podSecurityContext(sc *types.SecurityContext, secCtxConf SecurityContextCon
fsGroup = sc.FSGroup
}

if nonRoot == nil && user == nil && group == nil && fsGroup == nil {
if sc != nil {
seccomp = seccompProfile(sc.SeccompProfile)
}

if nonRoot == nil && user == nil && group == nil && fsGroup == nil && seccomp == nil {
return nil
}

securityContext := &v1.PodSecurityContext{
RunAsNonRoot: nonRoot,
RunAsUser: user,
RunAsGroup: group,
FSGroup: fsGroup,
RunAsNonRoot: nonRoot,
RunAsUser: user,
RunAsGroup: group,
FSGroup: fsGroup,
SeccompProfile: seccomp,
}
log.Trace().Msgf("pod security context that will be used: %v", securityContext)
return securityContext
}

func seccompProfile(scp *types.SecProfile) *v1.SeccompProfile {
if scp == nil || len(scp.Type) == 0 {
return nil
}
log.Trace().Msgf("using seccomp profile: %v", scp)

seccompProfile := &v1.SeccompProfile{
Type: v1.SeccompProfileType(scp.Type),
}
if len(scp.LocalhostProfile) > 0 {
seccompProfile.LocalhostProfile = &scp.LocalhostProfile
}

return seccompProfile
}

func containerSecurityContext(sc *types.SecurityContext, stepPrivileged bool) *v1.SecurityContext {
var privileged *bool

Expand All @@ -347,6 +380,36 @@ func containerSecurityContext(sc *types.SecurityContext, stepPrivileged bool) *v
return securityContext
}

func apparmorAnnotation(containerName string, scp *types.SecProfile) (*string, *string) {
if scp == nil {
return nil, nil
}
log.Trace().Msgf("using AppArmor profile: %v", scp)

var (
profileType string
profilePath string
)

if scp.Type == types.SecProfileTypeRuntimeDefault {
profileType = "runtime"
profilePath = "default"
}

if scp.Type == types.SecProfileTypeLocalhost {
profileType = "localhost"
profilePath = scp.LocalhostProfile
}

if len(profileType) == 0 {
return nil, nil
}

key := v1.AppArmorBetaContainerAnnotationKeyPrefix + containerName
value := profileType + "/" + profilePath
return &key, &value
}

func mapToEnvVars(m map[string]string) []v1.EnvVar {
var ev []v1.EnvVar
for k, v := range m {
Expand Down
34 changes: 24 additions & 10 deletions pipeline/backend/kubernetes/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ func TestFullPod(t *testing.T) {
"step": "go-test"
},
"annotations": {
"apparmor.security": "runtime/default"
"apps.kubernetes.io/pod-index": "0",
"container.apparmor.security.beta.kubernetes.io/wp-01he8bebctabr3kgk0qj36d2me-0": "localhost/k8s-apparmor-example-deny-write"
}
},
"spec": {
Expand Down Expand Up @@ -225,7 +226,11 @@ func TestFullPod(t *testing.T) {
"runAsUser": 101,
"runAsGroup": 101,
"runAsNonRoot": true,
"fsGroup": 101
"fsGroup": 101,
"seccompProfile": {
"type": "Localhost",
"localhostProfile": "profiles/audit.json"
}
},
"imagePullSecrets": [
{
Expand Down Expand Up @@ -264,6 +269,21 @@ func TestFullPod(t *testing.T) {
{Name: "cloudflare", IP: "1.1.1.1"},
{Name: "cf.v6", IP: "2606:4700:4700::64"},
}
secCtx := types.SecurityContext{
Privileged: newBool(true),
RunAsNonRoot: newBool(true),
RunAsUser: newInt64(101),
RunAsGroup: newInt64(101),
FSGroup: newInt64(101),
SeccompProfile: &types.SecProfile{
Type: "Localhost",
LocalhostProfile: "profiles/audit.json",
},
ApparmorProfile: &types.SecProfile{
Type: "Localhost",
LocalhostProfile: "k8s-apparmor-example-deny-write",
},
}
pod, err := mkPod(&types.Step{
Name: "go-test",
Image: "meltwater/drone-cache",
Expand All @@ -283,20 +303,14 @@ func TestFullPod(t *testing.T) {
Requests: map[string]string{"memory": "128Mi", "cpu": "1000m"},
Limits: map[string]string{"memory": "256Mi", "cpu": "2"},
},
SecurityContext: &types.SecurityContext{
Privileged: newBool(true),
RunAsNonRoot: newBool(true),
RunAsUser: newInt64(101),
RunAsGroup: newInt64(101),
FSGroup: newInt64(101),
},
SecurityContext: &secCtx,
},
},
}, &config{
Namespace: "woodpecker",
ImagePullSecretNames: []string{"regcred", "another-pull-secret"},
PodLabels: map[string]string{"app": "test"},
PodAnnotations: map[string]string{"apparmor.security": "runtime/default"},
PodAnnotations: map[string]string{"apps.kubernetes.io/pod-index": "0"},
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64")
assert.NoError(t, err)
Expand Down
24 changes: 19 additions & 5 deletions pipeline/backend/types/backend_kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,23 @@ const (
)

type SecurityContext struct {
Privileged *bool `json:"privileged,omitempty"`
RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"`
RunAsUser *int64 `json:"runAsUser,omitempty"`
RunAsGroup *int64 `json:"runAsGroup,omitempty"`
FSGroup *int64 `json:"fsGroup,omitempty"`
Privileged *bool `json:"privileged,omitempty"`
RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"`
RunAsUser *int64 `json:"runAsUser,omitempty"`
RunAsGroup *int64 `json:"runAsGroup,omitempty"`
FSGroup *int64 `json:"fsGroup,omitempty"`
SeccompProfile *SecProfile `json:"seccompProfile,omitempty"`
ApparmorProfile *SecProfile `json:"apparmorProfile,omitempty"`
}

type SecProfile struct {
Type SecProfileType `json:"type,omitempty"`
LocalhostProfile string `json:"localhostProfile,omitempty"`
}

type SecProfileType string

const (
SecProfileTypeRuntimeDefault SecProfileType = "RuntimeDefault"
SecProfileTypeLocalhost SecProfileType = "Localhost"
)
12 changes: 12 additions & 0 deletions pipeline/frontend/yaml/compiler/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,18 @@ func convertKubernetesBackendOptions(kubeOpt *yaml_types.KubernetesBackendOption
RunAsGroup: kubeOpt.SecurityContext.RunAsGroup,
FSGroup: kubeOpt.SecurityContext.FSGroup,
}
if kubeOpt.SecurityContext.SeccompProfile != nil {
securityContext.SeccompProfile = &backend_types.SecProfile{
Type: backend_types.SecProfileType(kubeOpt.SecurityContext.SeccompProfile.Type),
LocalhostProfile: kubeOpt.SecurityContext.SeccompProfile.LocalhostProfile,
}
}
if kubeOpt.SecurityContext.ApparmorProfile != nil {
securityContext.ApparmorProfile = &backend_types.SecProfile{
Type: backend_types.SecProfileType(kubeOpt.SecurityContext.ApparmorProfile.Type),
LocalhostProfile: kubeOpt.SecurityContext.ApparmorProfile.LocalhostProfile,
}
}
}

return backend_types.KubernetesBackendOptions{
Expand Down
18 changes: 18 additions & 0 deletions pipeline/frontend/yaml/linter/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,24 @@
},
"fsGroup": {
"type": "number"
},
"seccompProfile": {
"$ref": "#/definitions/step_backend_kubernetes_secprofile"
},
"apparmorProfile": {
"$ref": "#/definitions/step_backend_kubernetes_secprofile"
}
}
},
"step_backend_kubernetes_secprofile": {
"description": "Pods / containers security profile. Read more: https://woodpecker-ci.org/docs/administration/backends/kubernetes",
"type": "object",
"properties": {
"type": {
"type": "string"
},
"localhostProfile": {
"type": "string"
}
}
},
Expand Down
17 changes: 12 additions & 5 deletions pipeline/frontend/yaml/types/backend_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,16 @@ const (
)

type SecurityContext struct {
Privileged *bool `yaml:"privileged,omitempty"`
RunAsNonRoot *bool `yaml:"runAsNonRoot,omitempty"`
RunAsUser *int64 `yaml:"runAsUser,omitempty"`
RunAsGroup *int64 `yaml:"runAsGroup,omitempty"`
FSGroup *int64 `yaml:"fsGroup,omitempty"`
Privileged *bool `yaml:"privileged,omitempty"`
RunAsNonRoot *bool `yaml:"runAsNonRoot,omitempty"`
RunAsUser *int64 `yaml:"runAsUser,omitempty"`
RunAsGroup *int64 `yaml:"runAsGroup,omitempty"`
FSGroup *int64 `yaml:"fsGroup,omitempty"`
SeccompProfile *SecProfile `yaml:"seccompProfile,omitempty"`
ApparmorProfile *SecProfile `yaml:"apparmorProfile,omitempty"`
}

type SecProfile struct {
Type string `yaml:"type,omitempty"`
LocalhostProfile string `yaml:"localhostProfile,omitempty"`
}

0 comments on commit 9bbc446

Please sign in to comment.