Merge pull request #28300 from pweil-/psp-seccomp

Automatic merge from submit-queue

Add PSP support for seccomp profiles

Seccomp support for PSP.  There are still a couple of TODOs that need to be fixed but this is passing tests.

One thing of note, since seccomp is all being stored in annotations right now it breaks some of the assumptions we've stated for the provider in terms of mutating the passed in pod.  I've put big warning comments around the pieces that do that to make sure it's clear and covered the rollback in admission if the policy fails to validate.

@sttts @pmorie @erictune @smarterclayton @liggitt
pull/6/head
Kubernetes Submit Queue 2016-10-18 09:26:09 -07:00 committed by GitHub
commit c592a46e16
10 changed files with 723 additions and 3 deletions

View File

@ -2119,7 +2119,7 @@ func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath
return allErrs
}
func validateSeccompProfile(p string, fldPath *field.Path) field.ErrorList {
func ValidateSeccompProfile(p string, fldPath *field.Path) field.ErrorList {
if p == "docker/default" {
return nil
}
@ -2135,11 +2135,11 @@ func validateSeccompProfile(p string, fldPath *field.Path) field.ErrorList {
func ValidateSeccompPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if p, exists := annotations[api.SeccompPodAnnotationKey]; exists {
allErrs = append(allErrs, validateSeccompProfile(p, fldPath.Child(api.SeccompPodAnnotationKey))...)
allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(api.SeccompPodAnnotationKey))...)
}
for k, p := range annotations {
if strings.HasPrefix(k, api.SeccompContainerAnnotationKeyPrefix) {
allErrs = append(allErrs, validateSeccompProfile(p, fldPath.Child(k))...)
allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(k))...)
}
}

View File

@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/sets"
@ -600,6 +601,14 @@ func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string,
allErrs = append(allErrs, validatePodSecurityPolicySysctls(sysctlFldPath, sysctls)...)
}
if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" {
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...)
}
if allowed := annotations[seccomp.AllowedProfilesAnnotationKey]; allowed != "" {
for _, p := range strings.Split(allowed, ",") {
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.AllowedProfilesAnnotationKey))...)
}
}
return allErrs
}

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/validation/field"
@ -1604,6 +1605,15 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
invalidSysctlPattern := validPSP()
invalidSysctlPattern.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "a.*.b"
invalidSeccompDefault := validPSP()
invalidSeccompDefault.Annotations = map[string]string{
seccomp.DefaultProfileAnnotationKey: "not-good",
}
invalidSeccompAllowed := validPSP()
invalidSeccompAllowed.Annotations = map[string]string{
seccomp.AllowedProfilesAnnotationKey: "docker/default,not-good",
}
type testCase struct {
psp *extensions.PodSecurityPolicy
errorType field.ErrorType
@ -1700,6 +1710,16 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
errorType: field.ErrorTypeInvalid,
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
},
"invalid seccomp default profile": {
psp: invalidSeccompDefault,
errorType: field.ErrorTypeInvalid,
errorDetail: "must be a valid seccomp profile",
},
"invalid seccomp allowed profile": {
psp: invalidSeccompAllowed,
errorType: field.ErrorTypeInvalid,
errorDetail: "must be a valid seccomp profile",
},
}
for k, v := range errorCases {
@ -1768,6 +1788,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
withSysctl := validPSP()
withSysctl.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "net.*"
validSeccomp := validPSP()
validSeccomp.Annotations = map[string]string{
seccomp.DefaultProfileAnnotationKey: "docker/default",
seccomp.AllowedProfilesAnnotationKey: "docker/default,unconfined,localhost/foo",
}
successCases := map[string]struct {
psp *extensions.PodSecurityPolicy
}{
@ -1792,6 +1818,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
"with network sysctls": {
psp: withSysctl,
},
"valid seccomp annotations": {
psp: validSeccomp,
},
}
for k, v := range successCases {

View File

@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
@ -56,6 +57,11 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
errs = append(errs, err)
}
seccompStrat, err := createSeccompStrategy(psp)
if err != nil {
errs = append(errs, err)
}
fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup)
if err != nil {
errs = append(errs, err)
@ -92,6 +98,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
FSGroupStrategy: fsGroupStrat,
SupplementalGroupStrategy: supGroupStrat,
CapabilitiesStrategy: capStrat,
SeccompStrategy: seccompStrat,
SysctlsStrategy: sysctlsStrat,
}
@ -129,6 +136,11 @@ func createAppArmorStrategy(psp *extensions.PodSecurityPolicy) (apparmor.Strateg
return apparmor.NewStrategy(psp.Annotations), nil
}
// createSeccompStrategy creates a new seccomp strategy.
func createSeccompStrategy(psp *extensions.PodSecurityPolicy) (seccomp.Strategy, error) {
return seccomp.NewStrategy(psp.Annotations), nil
}
// createFSGroupStrategy creates a new fsgroup strategy
func createFSGroupStrategy(opts *extensions.FSGroupStrategyOptions) (group.GroupStrategy, error) {
switch opts.Rule {

View File

@ -103,6 +103,18 @@ func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurit
sc.SELinuxOptions = seLinux
}
// This is only generated on the pod level. Containers inherit the pod's profile. If the
// container has a specific profile set then it will be caught in the validation step.
seccompProfile, err := s.strategies.SeccompStrategy.Generate(annotations, pod)
if err != nil {
return nil, nil, err
}
if seccompProfile != "" {
if annotations == nil {
annotations = map[string]string{}
}
annotations[api.SeccompPodAnnotationKey] = seccompProfile
}
return sc, annotations, nil
}
@ -188,6 +200,7 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
}
allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(pod, fsGroups)...)
allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(pod, pod.Spec.SecurityContext.SupplementalGroups)...)
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...)
// make a dummy container context to reuse the selinux strategies
container := &api.Container{
@ -247,6 +260,7 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...)
if !s.psp.Spec.Privileged && *sc.Privileged {
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed"))

View File

@ -27,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/diff"
"k8s.io/kubernetes/pkg/util/validation/field"
@ -49,6 +50,10 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
return &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{
Name: "psp-sa",
Annotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: "*",
seccomp.DefaultProfileAnnotationKey: "foo",
},
},
Spec: extensions.PodSecurityPolicySpec{
DefaultAddCapabilities: []api.Capability{"foo"},
@ -121,6 +126,10 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
return &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{
Name: "psp-sa",
Annotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: "*",
seccomp.DefaultProfileAnnotationKey: "foo",
},
},
Spec: extensions.PodSecurityPolicySpec{
DefaultAddCapabilities: []api.Capability{"foo"},
@ -238,6 +247,9 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
failUnsafeSysctlFooPod := defaultPod()
failUnsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
failSeccompProfilePod := defaultPod()
failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"}
errorCases := map[string]struct {
pod *api.Pod
psp *extensions.PodSecurityPolicy
@ -313,6 +325,11 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
psp: failOtherSysctlsAllowedPSP,
expectedError: "sysctl \"foo\" is not allowed",
},
"failInvalidSeccomp": {
pod: failSeccompProfilePod,
psp: defaultPSP(),
expectedError: "Forbidden: seccomp may not be set",
},
}
for k, v := range errorCases {
provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory())
@ -382,6 +399,16 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
readOnlyRootFS := false
readOnlyRootFSPodFalse.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFS
failSeccompPod := defaultPod()
failSeccompPod.Annotations = map[string]string{
api.SeccompContainerAnnotationKeyPrefix + failSeccompPod.Spec.Containers[0].Name: "foo",
}
failSeccompPodInheritPodAnnotation := defaultPod()
failSeccompPodInheritPodAnnotation.Annotations = map[string]string{
api.SeccompPodAnnotationKey: "foo",
}
errorCases := map[string]struct {
pod *api.Pod
psp *extensions.PodSecurityPolicy
@ -432,6 +459,16 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
psp: readOnlyRootFSPSP,
expectedError: "ReadOnlyRootFilesystem must be set to true",
},
"failSeccompContainerAnnotation": {
pod: failSeccompPod,
psp: defaultPSP(),
expectedError: "Forbidden: seccomp may not be set",
},
"failSeccompContainerPodAnnotation": {
pod: failSeccompPodInheritPodAnnotation,
psp: defaultPSP(),
expectedError: "Forbidden: seccomp may not be set",
},
}
for k, v := range errorCases {
@ -512,6 +549,16 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
unsafeSysctlFooPod := defaultPod()
unsafeSysctlFooPod.Annotations[api.UnsafeSysctlsPodAnnotationKey] = "foo=1"
seccompPSP := defaultPSP()
seccompPSP.Annotations = map[string]string{
seccomp.AllowedProfilesAnnotationKey: "foo",
}
seccompPod := defaultPod()
seccompPod.Annotations = map[string]string{
api.SeccompPodAnnotationKey: "foo",
}
errorCases := map[string]struct {
pod *api.Pod
psp *extensions.PodSecurityPolicy
@ -556,6 +603,10 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
pod: unsafeSysctlFooPod,
psp: defaultPSP(),
},
"pass seccomp validating PSP": {
pod: seccompPod,
psp: seccompPSP,
},
}
for k, v := range errorCases {
@ -667,6 +718,21 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
readOnlyRootFSTrue := true
readOnlyRootFSPodTrue.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFSTrue
seccompPSP := defaultPSP()
seccompPSP.Annotations = map[string]string{
seccomp.AllowedProfilesAnnotationKey: "foo",
}
seccompPod := defaultPod()
seccompPod.Annotations = map[string]string{
api.SeccompContainerAnnotationKeyPrefix + seccompPod.Spec.Containers[0].Name: "foo",
}
seccompPodInherit := defaultPod()
seccompPodInherit.Annotations = map[string]string{
api.SeccompPodAnnotationKey: "foo",
}
errorCases := map[string]struct {
pod *api.Pod
psp *extensions.PodSecurityPolicy
@ -715,6 +781,14 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
pod: readOnlyRootFSPodTrue,
psp: defaultPSP(),
},
"pass seccomp container annotation": {
pod: seccompPod,
psp: seccompPSP,
},
"pass seccomp inherit pod annotation": {
pod: seccompPodInherit,
psp: seccompPSP,
},
}
for k, v := range errorCases {

View File

@ -0,0 +1,149 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package seccomp
import (
"fmt"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/validation/field"
)
const (
// AllowAny is the wildcard used to allow any profile.
AllowAny = "*"
// The annotation key specifying the default seccomp profile.
DefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName"
// The annotation key specifying the allowed seccomp profiles.
AllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames"
)
// Strategy defines the interface for all seccomp constraint strategies.
type Strategy interface {
// Generate returns a profile based on constraint rules.
Generate(annotations map[string]string, pod *api.Pod) (string, error)
// Validate ensures that the specified values fall within the range of the strategy.
ValidatePod(pod *api.Pod) field.ErrorList
// Validate ensures that the specified values fall within the range of the strategy.
ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList
}
type strategy struct {
defaultProfile string
allowedProfiles map[string]bool
// For printing error messages (preserves order).
allowedProfilesString string
// does the strategy allow any profile (wildcard)
allowAnyProfile bool
}
var _ Strategy = &strategy{}
// NewStrategy creates a new strategy that enforces seccomp profile constraints.
func NewStrategy(pspAnnotations map[string]string) Strategy {
var allowedProfiles map[string]bool
allowAnyProfile := false
if allowed, ok := pspAnnotations[AllowedProfilesAnnotationKey]; ok {
profiles := strings.Split(allowed, ",")
allowedProfiles = make(map[string]bool, len(profiles))
for _, p := range profiles {
if p == AllowAny {
allowAnyProfile = true
continue
}
allowedProfiles[p] = true
}
}
return &strategy{
defaultProfile: pspAnnotations[DefaultProfileAnnotationKey],
allowedProfiles: allowedProfiles,
allowedProfilesString: pspAnnotations[AllowedProfilesAnnotationKey],
allowAnyProfile: allowAnyProfile,
}
}
// Generate returns a profile based on constraint rules.
func (s *strategy) Generate(annotations map[string]string, pod *api.Pod) (string, error) {
if annotations[api.SeccompPodAnnotationKey] != "" {
// Profile already set, nothing to do.
return annotations[api.SeccompPodAnnotationKey], nil
}
return s.defaultProfile, nil
}
// ValidatePod ensures that the specified values on the pod fall within the range
// of the strategy.
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 !s.allowAnyProfile && len(s.allowedProfiles) == 0 && podProfile != "" {
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, "seccomp may not be set"))
return allErrs
}
if !s.profileAllowed(podProfile) {
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", podProfile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, msg))
}
return allErrs
}
// ValidateContainer ensures that the specified values on the container fall within
// the range of the strategy.
func (s *strategy) ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList {
allErrs := field.ErrorList{}
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompContainerAnnotationKeyPrefix + container.Name)
containerProfile := profileForContainer(pod, container)
if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && containerProfile != "" {
allErrs = append(allErrs, field.Forbidden(fieldPath, "seccomp may not be set"))
return allErrs
}
if !s.profileAllowed(containerProfile) {
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", containerProfile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(fieldPath, msg))
}
return allErrs
}
// profileAllowed checks if profile is in allowedProfiles or if allowedProfiles
// contains the wildcard.
func (s *strategy) profileAllowed(profile string) bool {
// for backwards compatibility and PSPs without a defined list of allowed profiles.
// If a PSP does not have allowedProfiles set then we should allow an empty profile.
// This will mean that the runtime default is used.
if len(s.allowedProfiles) == 0 && profile == "" {
return true
}
return s.allowAnyProfile || s.allowedProfiles[profile]
}
// profileForContainer returns the container profile if set, otherwise the pod profile.
func profileForContainer(pod *api.Pod, container *api.Container) string {
containerProfile, ok := pod.Annotations[api.SeccompContainerAnnotationKeyPrefix+container.Name]
if ok {
return containerProfile
}
return pod.Annotations[api.SeccompPodAnnotationKey]
}

View File

@ -0,0 +1,321 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package seccomp
import (
"reflect"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
)
var (
withoutSeccomp = map[string]string{"foo": "bar"}
allowAnyNoDefault = map[string]string{
AllowedProfilesAnnotationKey: "*",
}
allowAnyDefault = map[string]string{
AllowedProfilesAnnotationKey: "*",
DefaultProfileAnnotationKey: "foo",
}
allowAnyAndSpecificDefault = map[string]string{
AllowedProfilesAnnotationKey: "*,bar",
DefaultProfileAnnotationKey: "foo",
}
allowSpecific = map[string]string{
AllowedProfilesAnnotationKey: "foo",
}
)
func TestNewStrategy(t *testing.T) {
tests := map[string]struct {
annotations map[string]string
expectedAllowedProfilesString string
expectedAllowAny bool
expectedAllowedProfiles map[string]bool
expectedDefaultProfile string
}{
"no seccomp": {
annotations: withoutSeccomp,
expectedAllowAny: false,
expectedAllowedProfilesString: "",
expectedAllowedProfiles: nil,
expectedDefaultProfile: "",
},
"allow any, no default": {
annotations: allowAnyNoDefault,
expectedAllowAny: true,
expectedAllowedProfilesString: "*",
expectedAllowedProfiles: map[string]bool{},
expectedDefaultProfile: "",
},
"allow any, default": {
annotations: allowAnyDefault,
expectedAllowAny: true,
expectedAllowedProfilesString: "*",
expectedAllowedProfiles: map[string]bool{},
expectedDefaultProfile: "foo",
},
"allow any and specific, default": {
annotations: allowAnyAndSpecificDefault,
expectedAllowAny: true,
expectedAllowedProfilesString: "*,bar",
expectedAllowedProfiles: map[string]bool{
"bar": true,
},
expectedDefaultProfile: "foo",
},
}
for k, v := range tests {
strat := NewStrategy(v.annotations)
internalStrat, _ := strat.(*strategy)
if internalStrat.allowAnyProfile != v.expectedAllowAny {
t.Errorf("%s expected allowAnyProfile to be %t but found %t", k, v.expectedAllowAny, internalStrat.allowAnyProfile)
}
if internalStrat.allowedProfilesString != v.expectedAllowedProfilesString {
t.Errorf("%s expected allowedProfilesString to be %s but found %s", k, v.expectedAllowedProfilesString, internalStrat.allowedProfilesString)
}
if internalStrat.defaultProfile != v.expectedDefaultProfile {
t.Errorf("%s expected defaultProfile to be %s but found %s", k, v.expectedDefaultProfile, internalStrat.defaultProfile)
}
if !reflect.DeepEqual(v.expectedAllowedProfiles, internalStrat.allowedProfiles) {
t.Errorf("%s expected expectedAllowedProfiles to be %#v but found %#v", k, v.expectedAllowedProfiles, internalStrat.allowedProfiles)
}
}
}
func TestGenerate(t *testing.T) {
tests := map[string]struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectedProfile string
}{
"no seccomp, no pod annotations": {
pspAnnotations: withoutSeccomp,
podAnnotations: nil,
expectedProfile: "",
},
"no seccomp, pod annotations": {
pspAnnotations: withoutSeccomp,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedProfile: "foo",
},
"seccomp with no default, no pod annotations": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: nil,
expectedProfile: "",
},
"seccomp with no default, pod annotations": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedProfile: "foo",
},
"seccomp with default, no pod annotations": {
pspAnnotations: allowAnyDefault,
podAnnotations: nil,
expectedProfile: "foo",
},
"seccomp with default, pod annotations": {
pspAnnotations: allowAnyDefault,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "bar",
},
expectedProfile: "bar",
},
}
for k, v := range tests {
strat := NewStrategy(v.pspAnnotations)
actual, err := strat.Generate(v.podAnnotations, nil)
if err != nil {
t.Errorf("%s received error during generation %#v", k, err)
continue
}
if actual != v.expectedProfile {
t.Errorf("%s expected profile %s but received %s", k, v.expectedProfile, actual)
}
}
}
func TestValidatePod(t *testing.T) {
tests := map[string]struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectedError string
}{
"no pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: nil,
expectedError: "Forbidden: is not an allowed seccomp profile. Valid values are foo",
},
"no pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: nil,
expectedError: "",
},
"valid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "",
},
"invalid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "bar",
},
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
},
"pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "Forbidden: seccomp may not be set",
},
"pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "",
},
"no pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: nil,
expectedError: "",
},
}
for k, v := range tests {
pod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: v.podAnnotations,
},
}
strat := NewStrategy(v.pspAnnotations)
errs := strat.ValidatePod(pod)
if v.expectedError == "" && len(errs) != 0 {
t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 0 {
t.Errorf("%s expected error %s but received none", k, v.expectedError)
}
if v.expectedError != "" && len(errs) > 1 {
t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) {
t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error())
}
}
}
func TestValidateContainer(t *testing.T) {
tests := map[string]struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectedError string
}{
"no pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: nil,
expectedError: "Forbidden: is not an allowed seccomp profile. Valid values are foo",
},
"no pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: nil,
expectedError: "",
},
"valid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
},
expectedError: "",
},
"invalid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "bar",
},
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
},
"pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
},
expectedError: "Forbidden: seccomp may not be set",
},
"pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
},
expectedError: "",
},
"no pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: nil,
expectedError: "",
},
"container inherits valid pod annotation": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "",
},
"container inherits invalid pod annotation": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "bar",
},
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
},
}
for k, v := range tests {
pod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: v.podAnnotations,
},
}
container := &api.Container{
Name: "container",
}
strat := NewStrategy(v.pspAnnotations)
errs := strat.ValidateContainer(pod, container)
if v.expectedError == "" && len(errs) != 0 {
t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 0 {
t.Errorf("%s expected error %s but received none", k, v.expectedError)
}
if v.expectedError != "" && len(errs) > 1 {
t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) {
t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error())
}
}
}

View File

@ -22,6 +22,7 @@ import (
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
@ -65,4 +66,5 @@ type ProviderStrategies struct {
SupplementalGroupStrategy group.GroupStrategy
CapabilitiesStrategy capabilities.Strategy
SysctlsStrategy sysctl.SysctlsStrategy
SeccompStrategy seccomp.Strategy
}

View File

@ -33,6 +33,7 @@ import (
clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/security/apparmor"
kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/util/diff"
)
@ -55,6 +56,115 @@ func useInitContainers(pod *kapi.Pod) *kapi.Pod {
return pod
}
func TestAdmitSeccomp(t *testing.T) {
containerName := "container"
tests := map[string]struct {
pspAnnotations map[string]string
podAnnotations map[string]string
shouldAdmit bool
}{
"no seccomp, no pod annotations": {
pspAnnotations: nil,
podAnnotations: nil,
shouldAdmit: true,
},
"no seccomp, pod annotations": {
pspAnnotations: nil,
podAnnotations: map[string]string{
kapi.SeccompPodAnnotationKey: "foo",
},
shouldAdmit: false,
},
"no seccomp, container annotations": {
pspAnnotations: nil,
podAnnotations: map[string]string{
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo",
},
shouldAdmit: false,
},
"seccomp, allow any no pod annotation": {
pspAnnotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny,
},
podAnnotations: nil,
shouldAdmit: true,
},
"seccomp, allow any pod annotation": {
pspAnnotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny,
},
podAnnotations: map[string]string{
kapi.SeccompPodAnnotationKey: "foo",
},
shouldAdmit: true,
},
"seccomp, allow any container annotation": {
pspAnnotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny,
},
podAnnotations: map[string]string{
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo",
},
shouldAdmit: true,
},
"seccomp, allow specific pod annotation failure": {
pspAnnotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: "foo",
},
podAnnotations: map[string]string{
kapi.SeccompPodAnnotationKey: "bar",
},
shouldAdmit: false,
},
"seccomp, allow specific container annotation failure": {
pspAnnotations: map[string]string{
// provide a default so we don't have to give the pod annotation
seccomp.DefaultProfileAnnotationKey: "foo",
seccomp.AllowedProfilesAnnotationKey: "foo",
},
podAnnotations: map[string]string{
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "bar",
},
shouldAdmit: false,
},
"seccomp, allow specific pod annotation pass": {
pspAnnotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: "foo",
},
podAnnotations: map[string]string{
kapi.SeccompPodAnnotationKey: "foo",
},
shouldAdmit: true,
},
"seccomp, allow specific container annotation pass": {
pspAnnotations: map[string]string{
// provide a default so we don't have to give the pod annotation
seccomp.DefaultProfileAnnotationKey: "foo",
seccomp.AllowedProfilesAnnotationKey: "foo,bar",
},
podAnnotations: map[string]string{
kapi.SeccompContainerAnnotationKeyPrefix + containerName: "bar",
},
shouldAdmit: true,
},
}
for k, v := range tests {
psp := restrictivePSP()
psp.Annotations = v.pspAnnotations
pod := &kapi.Pod{
ObjectMeta: kapi.ObjectMeta{
Annotations: v.podAnnotations,
},
Spec: kapi.PodSpec{
Containers: []kapi.Container{
{Name: containerName},
},
},
}
testPSPAdmit(k, []*extensions.PodSecurityPolicy{psp}, pod, v.shouldAdmit, psp.Name, t)
}
}
func TestAdmitPrivileged(t *testing.T) {
createPodWithPriv := func(priv bool) *kapi.Pod {
pod := goodPod()