diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index 2230f5f5301..260414d5bc7 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -745,3 +745,58 @@ func setHostnameAsFQDNInUse(podSpec *api.PodSpec) bool { } return *podSpec.SetHostnameAsFQDN } + +// SeccompAnnotationForField takes a pod seccomp profile field and returns the +// converted annotation value +func SeccompAnnotationForField(field *api.SeccompProfile) string { + // If only seccomp fields are specified, add the corresponding annotations. + // This ensures that the fields are enforced even if the node version + // trails the API version + switch field.Type { + case api.SeccompProfileTypeUnconfined: + return v1.SeccompProfileNameUnconfined + + case api.SeccompProfileTypeRuntimeDefault: + return v1.SeccompProfileRuntimeDefault + + case api.SeccompProfileTypeLocalhost: + if field.LocalhostProfile != nil { + return v1.SeccompLocalhostProfileNamePrefix + *field.LocalhostProfile + } + } + + // we can only reach this code path if the LocalhostProfile is nil but the + // provided field type is SeccompProfileTypeLocalhost or if an unrecognized + // type is specified + return "" +} + +// SeccompFieldForAnnotation takes a pod annotation and returns the converted +// seccomp profile field. +func SeccompFieldForAnnotation(annotation string) *api.SeccompProfile { + // If only seccomp annotations are specified, copy the values into the + // corresponding fields. This ensures that existing applications continue + // to enforce seccomp, and prevents the kubelet from needing to resolve + // annotations & fields. + if annotation == v1.SeccompProfileNameUnconfined { + return &api.SeccompProfile{Type: api.SeccompProfileTypeUnconfined} + } + + if annotation == api.SeccompProfileRuntimeDefault || annotation == api.DeprecatedSeccompProfileDockerDefault { + return &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault} + } + + if strings.HasPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix) { + localhostProfile := strings.TrimPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix) + if localhostProfile != "" { + return &api.SeccompProfile{ + Type: api.SeccompProfileTypeLocalhost, + LocalhostProfile: &localhostProfile, + } + } + } + + // we can only reach this code path if the localhostProfile name has a zero + // length or if the annotation has an unrecognized value + return nil +} diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index 917101548f5..ea4476200b3 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -587,7 +587,7 @@ func applySeccompVersionSkew(pod *api.Pod) { // sync field and annotation if hasField && !hasAnnotation { - newAnnotation := seccompAnnotationForField(field) + newAnnotation := podutil.SeccompAnnotationForField(field) if newAnnotation != "" { if pod.Annotations == nil { @@ -596,7 +596,7 @@ func applySeccompVersionSkew(pod *api.Pod) { pod.Annotations[v1.SeccompPodAnnotationKey] = newAnnotation } } else if hasAnnotation && !hasField { - newField := seccompFieldForAnnotation(annotation) + newField := podutil.SeccompFieldForAnnotation(annotation) if newField != nil { if pod.Spec.SecurityContext == nil { @@ -621,7 +621,7 @@ func applySeccompVersionSkew(pod *api.Pod) { // sync field and annotation if hasField && !hasAnnotation { - newAnnotation := seccompAnnotationForField(field) + newAnnotation := podutil.SeccompAnnotationForField(field) if newAnnotation != "" { if pod.Annotations == nil { @@ -630,7 +630,7 @@ func applySeccompVersionSkew(pod *api.Pod) { pod.Annotations[key] = newAnnotation } } else if hasAnnotation && !hasField { - newField := seccompFieldForAnnotation(annotation) + newField := podutil.SeccompFieldForAnnotation(annotation) if newField != nil { if ctr.SecurityContext == nil { @@ -643,58 +643,3 @@ func applySeccompVersionSkew(pod *api.Pod) { return true }) } - -// seccompFieldForAnnotation takes a pod seccomp profile field and returns the -// converted annotation value -func seccompAnnotationForField(field *api.SeccompProfile) string { - // If only seccomp fields are specified, add the corresponding annotations. - // This ensures that the fields are enforced even if the node version - // trails the API version - switch field.Type { - case api.SeccompProfileTypeUnconfined: - return v1.SeccompProfileNameUnconfined - - case api.SeccompProfileTypeRuntimeDefault: - return v1.SeccompProfileRuntimeDefault - - case api.SeccompProfileTypeLocalhost: - if field.LocalhostProfile != nil { - return v1.SeccompLocalhostProfileNamePrefix + *field.LocalhostProfile - } - } - - // we can only reach this code path if the LocalhostProfile is nil but the - // provided field type is SeccompProfileTypeLocalhost or if an unrecognized - // type is specified - return "" -} - -// seccompFieldForAnnotation takes a pod annotation and returns the converted -// seccomp profile field. -func seccompFieldForAnnotation(annotation string) *api.SeccompProfile { - // If only seccomp annotations are specified, copy the values into the - // corresponding fields. This ensures that existing applications continue - // to enforce seccomp, and prevents the kubelet from needing to resolve - // annotations & fields. - if annotation == v1.SeccompProfileNameUnconfined { - return &api.SeccompProfile{Type: api.SeccompProfileTypeUnconfined} - } - - if annotation == api.SeccompProfileRuntimeDefault || annotation == api.DeprecatedSeccompProfileDockerDefault { - return &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault} - } - - if strings.HasPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix) { - localhostProfile := strings.TrimPrefix(annotation, v1.SeccompLocalhostProfileNamePrefix) - if localhostProfile != "" { - return &api.SeccompProfile{ - Type: api.SeccompProfileTypeLocalhost, - LocalhostProfile: &localhostProfile, - } - } - } - - // we can only reach this code path if the localhostProfile name has a zero - // length or if the annotation has an unrecognized value - return nil -} diff --git a/pkg/security/podsecuritypolicy/seccomp/BUILD b/pkg/security/podsecuritypolicy/seccomp/BUILD index 49f4a846c26..bce686a3621 100644 --- a/pkg/security/podsecuritypolicy/seccomp/BUILD +++ b/pkg/security/podsecuritypolicy/seccomp/BUILD @@ -11,6 +11,7 @@ go_library( srcs = ["strategy.go"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp", deps = [ + "//pkg/api/pod:go_default_library", "//pkg/apis/core:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], @@ -22,6 +23,7 @@ go_test( embed = [":go_default_library"], deps = [ "//pkg/apis/core:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/seccomp/strategy.go b/pkg/security/podsecuritypolicy/seccomp/strategy.go index 0c8d3faf9e5..5fee30cb1f4 100644 --- a/pkg/security/podsecuritypolicy/seccomp/strategy.go +++ b/pkg/security/podsecuritypolicy/seccomp/strategy.go @@ -21,6 +21,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/validation/field" + podutil "k8s.io/kubernetes/pkg/api/pod" api "k8s.io/kubernetes/pkg/apis/core" ) @@ -83,6 +84,10 @@ func (s *strategy) Generate(annotations map[string]string, pod *api.Pod) (string // Profile already set, nothing to do. return annotations[api.SeccompPodAnnotationKey], nil } + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { + // Profile field already set, translate to annotation + return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile), nil + } return s.defaultProfile, nil } @@ -92,6 +97,10 @@ func (s *strategy) ValidatePod(pod *api.Pod) field.ErrorList { allErrs := field.ErrorList{} podSpecFieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompPodAnnotationKey) podProfile := pod.Annotations[api.SeccompPodAnnotationKey] + // if the annotation is not set, see if the field is set and derive the corresponding annotation value + if len(podProfile) == 0 && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { + podProfile = podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile) + } if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && podProfile != "" { allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, "seccomp may not be set")) @@ -141,9 +150,19 @@ func (s *strategy) profileAllowed(profile string) bool { // profileForContainer returns the container profile if set, otherwise the pod profile. func profileForContainer(pod *api.Pod, container *api.Container) string { + if container.SecurityContext != nil && container.SecurityContext.SeccompProfile != nil { + // derive the annotation value from the container field + return podutil.SeccompAnnotationForField(container.SecurityContext.SeccompProfile) + } containerProfile, ok := pod.Annotations[api.SeccompContainerAnnotationKeyPrefix+container.Name] if ok { + // return the existing container annotation return containerProfile } + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil { + // derive the annotation value from the pod field + return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile) + } + // return the existing pod annotation return pod.Annotations[api.SeccompPodAnnotationKey] } diff --git a/pkg/security/podsecuritypolicy/seccomp/strategy_test.go b/pkg/security/podsecuritypolicy/seccomp/strategy_test.go index d1f41c14c89..4ecfc325084 100644 --- a/pkg/security/podsecuritypolicy/seccomp/strategy_test.go +++ b/pkg/security/podsecuritypolicy/seccomp/strategy_test.go @@ -21,6 +21,7 @@ import ( "strings" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" api "k8s.io/kubernetes/pkg/apis/core" ) @@ -41,6 +42,9 @@ var ( allowSpecific = map[string]string{ AllowedProfilesAnnotationKey: "foo", } + allowSpecificLocalhost = map[string]string{ + AllowedProfilesAnnotationKey: v1.SeccompLocalhostProfileNamePrefix + "foo", + } ) func TestNewStrategy(t *testing.T) { @@ -102,9 +106,11 @@ func TestNewStrategy(t *testing.T) { } func TestGenerate(t *testing.T) { + bar := "bar" tests := map[string]struct { pspAnnotations map[string]string podAnnotations map[string]string + seccompProfile *api.SeccompProfile expectedProfile string }{ "no seccomp, no pod annotations": { @@ -143,10 +149,25 @@ func TestGenerate(t *testing.T) { }, expectedProfile: "bar", }, + "seccomp with default, pod field": { + pspAnnotations: allowAnyDefault, + seccompProfile: &api.SeccompProfile{ + Type: api.SeccompProfileTypeLocalhost, + LocalhostProfile: &bar, + }, + expectedProfile: "localhost/bar", + }, } for k, v := range tests { s := NewStrategy(v.pspAnnotations) - actual, err := s.Generate(v.podAnnotations, nil) + actual, err := s.Generate(v.podAnnotations, &api.Pod{ + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + SeccompProfile: v.seccompProfile, + }, + }, + }) + if err != nil { t.Errorf("%s received error during generation %#v", k, err) continue @@ -158,9 +179,11 @@ func TestGenerate(t *testing.T) { } func TestValidatePod(t *testing.T) { + foo := "foo" tests := map[string]struct { pspAnnotations map[string]string podAnnotations map[string]string + seccompProfile *api.SeccompProfile expectedError string }{ "no pod annotations, required profiles": { @@ -206,12 +229,44 @@ func TestValidatePod(t *testing.T) { podAnnotations: nil, expectedError: "", }, + "valid pod annotations and field, required profiles": { + pspAnnotations: allowSpecific, + podAnnotations: map[string]string{ + api.SeccompPodAnnotationKey: "foo", + }, + seccompProfile: &api.SeccompProfile{ + Type: api.SeccompProfileTypeLocalhost, + LocalhostProfile: &foo, + }, + expectedError: "", + }, + "valid pod field and no annotation, required profiles": { + pspAnnotations: allowSpecific, + seccompProfile: &api.SeccompProfile{ + Type: api.SeccompProfileTypeLocalhost, + LocalhostProfile: &foo, + }, + expectedError: "Forbidden: localhost/foo is not an allowed seccomp profile. Valid values are foo", + }, + "valid pod field and no annotation, required profiles (localhost)": { + pspAnnotations: allowSpecificLocalhost, + seccompProfile: &api.SeccompProfile{ + Type: api.SeccompProfileTypeLocalhost, + LocalhostProfile: &foo, + }, + expectedError: "", + }, } for k, v := range tests { pod := &api.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: v.podAnnotations, }, + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + SeccompProfile: v.seccompProfile, + }, + }, } s := NewStrategy(v.pspAnnotations) errs := s.ValidatePod(pod) @@ -231,9 +286,12 @@ func TestValidatePod(t *testing.T) { } func TestValidateContainer(t *testing.T) { + foo := "foo" + bar := "bar" tests := map[string]struct { pspAnnotations map[string]string podAnnotations map[string]string + seccompProfile *api.SeccompProfile expectedError string }{ "no pod annotations, required profiles": { @@ -293,6 +351,22 @@ func TestValidateContainer(t *testing.T) { }, expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo", }, + "valid container field and no annotation, required profiles": { + pspAnnotations: allowSpecificLocalhost, + seccompProfile: &api.SeccompProfile{ + Type: api.SeccompProfileTypeLocalhost, + LocalhostProfile: &foo, + }, + expectedError: "", + }, + "invalid container field and no annotation, required profiles": { + pspAnnotations: allowSpecificLocalhost, + seccompProfile: &api.SeccompProfile{ + Type: api.SeccompProfileTypeLocalhost, + LocalhostProfile: &bar, + }, + expectedError: "Forbidden: localhost/bar is not an allowed seccomp profile. Valid values are localhost/foo", + }, } for k, v := range tests { pod := &api.Pod{ @@ -302,6 +376,9 @@ func TestValidateContainer(t *testing.T) { } container := &api.Container{ Name: "container", + SecurityContext: &api.SecurityContext{ + SeccompProfile: v.seccompProfile, + }, } s := NewStrategy(v.pspAnnotations) diff --git a/test/e2e/framework/.import-restrictions b/test/e2e/framework/.import-restrictions index 9b0fc023db9..207dc8f8c15 100644 --- a/test/e2e/framework/.import-restrictions +++ b/test/e2e/framework/.import-restrictions @@ -6,6 +6,7 @@ rules: - k8s.io/kubernetes/pkg/api/v1/pod - k8s.io/kubernetes/pkg/api/v1/resource - k8s.io/kubernetes/pkg/api/v1/service + - k8s.io/kubernetes/pkg/api/pod - k8s.io/kubernetes/pkg/apis/apps - k8s.io/kubernetes/pkg/apis/apps/validation - k8s.io/kubernetes/pkg/apis/autoscaling