You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	Kubernetes AppArmor and seccomp (#3123)
Closes #2545
seccomp
https://kubernetes.io/docs/tutorials/security/seccomp/
https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/135-seccomp/README.md
AppArmor
https://kubernetes.io/docs/tutorials/security/apparmor/
fddcbb9cbf/keps/sig-node/24-apparmor/README.md
Went ahead and implemented API from KEP-24 above.
			
			
This commit is contained in:
		| @@ -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) | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
| @@ -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 { | ||||
| @@ -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 | ||||
|  | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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": { | ||||
| @@ -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": [ | ||||
| 				{ | ||||
| @@ -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", | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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" | ||||
| ) | ||||
|   | ||||
| @@ -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{ | ||||
|   | ||||
| @@ -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" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|   | ||||
| @@ -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"` | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user