Skip to content

Commit

Permalink
Merge pull request kubernetes#92856 from saschagrunert/psp-seccomp-ga
Browse files Browse the repository at this point in the history
Implement PodSecurityPolicy enforcement for seccomp GA
  • Loading branch information
k8s-ci-robot authored Jul 11, 2020
2 parents 60b8693 + 96fb83c commit 70f68db
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 60 deletions.
55 changes: 55 additions & 0 deletions pkg/api/pod/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
63 changes: 4 additions & 59 deletions pkg/registry/core/pod/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
2 changes: 2 additions & 0 deletions pkg/security/podsecuritypolicy/seccomp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand All @@ -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",
],
)
Expand Down
19 changes: 19 additions & 0 deletions pkg/security/podsecuritypolicy/seccomp/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
}

Expand All @@ -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"))
Expand Down Expand Up @@ -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]
}
79 changes: 78 additions & 1 deletion pkg/security/podsecuritypolicy/seccomp/strategy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -41,6 +42,9 @@ var (
allowSpecific = map[string]string{
AllowedProfilesAnnotationKey: "foo",
}
allowSpecificLocalhost = map[string]string{
AllowedProfilesAnnotationKey: v1.SeccompLocalhostProfileNamePrefix + "foo",
}
)

func TestNewStrategy(t *testing.T) {
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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
Expand All @@ -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": {
Expand Down Expand Up @@ -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)
Expand All @@ -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": {
Expand Down Expand Up @@ -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{
Expand All @@ -302,6 +376,9 @@ func TestValidateContainer(t *testing.T) {
}
container := &api.Container{
Name: "container",
SecurityContext: &api.SecurityContext{
SeccompProfile: v.seccompProfile,
},
}

s := NewStrategy(v.pspAnnotations)
Expand Down
1 change: 1 addition & 0 deletions test/e2e/framework/.import-restrictions
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 70f68db

Please sign in to comment.