PodSecurityPolicy: Order by name, prefer non-mutating policies, require *api.Pod, allow GC updates

pull/6/head
Jordan Liggitt 2017-10-05 15:56:12 -04:00
parent a5f722e181
commit 8c5b01376a
No known key found for this signature in database
GPG Key ID: 39928704103C7229
3 changed files with 550 additions and 247 deletions

View File

@ -16,12 +16,12 @@ go_library(
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/extensions/internalversion:go_default_library", "//pkg/client/listers/extensions/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library", "//pkg/kubeapiserver/admission:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/security/podsecuritypolicy:go_default_library", "//pkg/security/podsecuritypolicy:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/serviceaccount:go_default_library", "//pkg/serviceaccount:go_default_library",
"//pkg/util/maps:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
@ -48,6 +48,8 @@ go_test(
"//pkg/security/podsecuritypolicy/seccomp:go_default_library", "//pkg/security/podsecuritypolicy/seccomp:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",

View File

@ -19,10 +19,12 @@ package podsecuritypolicy
import ( import (
"fmt" "fmt"
"io" "io"
"sort"
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
@ -34,11 +36,10 @@ import (
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
sc "k8s.io/kubernetes/pkg/securitycontext"
"k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/kubernetes/pkg/util/maps"
) )
const ( const (
@ -120,8 +121,17 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
} }
pod, ok := a.GetObject().(*api.Pod) pod, ok := a.GetObject().(*api.Pod)
// if we can't convert then we don't handle this object so just return // if we can't convert then fail closed since we've already checked that this is supposed to be a pod object.
// this shouldn't normally happen during admission but could happen if an integrator passes a versioned
// pod object rather than an internal object.
if !ok { if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
}
// if this is an update, see if we are only updating the ownerRef/finalizers. Garbage collection does this
// and we should allow it in general, since you had the power to update and the power to delete.
// The worst that happens is that you delete something, but you aren't controlling the privileged object itself
if a.GetOperation() == admission.Update && rbacregistry.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), apiequality.Semantic) {
return nil return nil
} }
@ -143,32 +153,70 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
return nil return nil
} }
// sort by name to make order deterministic
// TODO(liggitt): add priority field to allow admins to bucket differently
sort.SliceStable(matchedPolicies, func(i, j int) bool {
return strings.Compare(matchedPolicies[i].Name, matchedPolicies[j].Name) < 0
})
providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace)
logProviders(pod, providers, errs) logProviders(a, pod, providers, errs)
if len(providers) == 0 { if len(providers) == 0 {
return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request")) return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request"))
} }
// TODO(liggitt): allow spec mutation during initializing updates?
specMutationAllowed := a.GetOperation() == admission.Create
// all containers in a single pod must validate under a single provider or we will reject the request // all containers in a single pod must validate under a single provider or we will reject the request
validationErrs := field.ErrorList{} validationErrs := field.ErrorList{}
var (
allowedPod *api.Pod
allowingProvider psp.Provider
)
loop:
for _, provider := range providers { for _, provider := range providers {
if errs := assignSecurityContext(provider, pod, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 { podCopy := pod.DeepCopy()
if errs := assignSecurityContext(provider, podCopy, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 {
validationErrs = append(validationErrs, errs...) validationErrs = append(validationErrs, errs...)
continue continue
} }
// the entire pod validated, annotate and accept the pod // the entire pod validated
glog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, provider.GetPSPName())
switch {
case apiequality.Semantic.DeepEqual(pod, podCopy):
// if it validated without mutating anything, use this result
allowedPod = podCopy
allowingProvider = provider
break loop
case specMutationAllowed && allowedPod == nil:
// if mutation is allowed and this is the first PSP to allow the pod, remember it,
// but continue to see if another PSP allows without mutating
allowedPod = podCopy
allowingProvider = provider
glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s with mutation", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName())
case !specMutationAllowed:
glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s, but required mutation, skipping", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName())
}
}
if allowedPod != nil {
*pod = *allowedPod
// annotate and accept the pod
glog.V(4).Infof("pod %s (generate: %s) in namespace %s validated against provider %s", pod.Name, pod.GenerateName, a.GetNamespace(), allowingProvider.GetPSPName())
if pod.ObjectMeta.Annotations == nil { if pod.ObjectMeta.Annotations == nil {
pod.ObjectMeta.Annotations = map[string]string{} pod.ObjectMeta.Annotations = map[string]string{}
} }
pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.GetPSPName() pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = allowingProvider.GetPSPName()
return nil return nil
} }
// we didn't validate against any provider, reject the pod and give the errors for each attempt // we didn't validate against any provider, reject the pod and give the errors for each attempt
glog.V(4).Infof("unable to validate pod %s (generate: %s) against any pod security policy: %v", pod.Name, pod.GenerateName, validationErrs) glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs)
return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs)) return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs))
} }
@ -176,82 +224,43 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
// and validates that the sc falls within the psp constraints. All containers must validate against // and validates that the sc falls within the psp constraints. All containers must validate against
// the same psp or is not considered valid. // the same psp or is not considered valid.
func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.Path) field.ErrorList { func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.Path) field.ErrorList {
generatedSCs := make([]*api.SecurityContext, len(pod.Spec.Containers))
var generatedInitSCs []*api.SecurityContext
errs := field.ErrorList{} errs := field.ErrorList{}
psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod) psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod)
if err != nil { if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error())) errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error()))
} }
// save the original PSC and validate the generated PSC. Leave the generated PSC
// set for container generation/validation. We will reset to original post container
// validation.
originalPSC := pod.Spec.SecurityContext
pod.Spec.SecurityContext = psc pod.Spec.SecurityContext = psc
originalAnnotations := maps.CopySS(pod.Annotations)
pod.Annotations = pscAnnotations pod.Annotations = pscAnnotations
errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...) errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...)
// Note: this is not changing the original container, we will set container SCs later so long for i := range pod.Spec.InitContainers {
// as all containers validated under the same PSP. sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.InitContainers[i])
for i, containerCopy := range pod.Spec.InitContainers {
// We will determine the effective security context for the container and validate against that
// since that is how the sc provider will eventually apply settings in the runtime.
// This results in an SC that is based on the Pod's PSC with the set fields from the container
// overriding pod level settings.
containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy)
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy)
if err != nil { if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error())) errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error()))
continue continue
} }
generatedInitSCs = append(generatedInitSCs, sc) pod.Spec.InitContainers[i].SecurityContext = sc
containerCopy.SecurityContext = sc
pod.Annotations = scAnnotations pod.Annotations = scAnnotations
errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...) errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.InitContainers[i], field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...)
} }
// Note: this is not changing the original container, we will set container SCs later so long for i := range pod.Spec.Containers {
// as all containers validated under the same PSP. sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[i])
for i, containerCopy := range pod.Spec.Containers {
// We will determine the effective security context for the container and validate against that
// since that is how the sc provider will eventually apply settings in the runtime.
// This results in an SC that is based on the Pod's PSC with the set fields from the container
// overriding pod level settings.
containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy)
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy)
if err != nil { if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error())) errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error()))
continue continue
} }
generatedSCs[i] = sc
containerCopy.SecurityContext = sc pod.Spec.Containers[i].SecurityContext = sc
pod.Annotations = scAnnotations pod.Annotations = scAnnotations
errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "containers").Index(i).Child("securityContext"))...) errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.Containers[i], field.NewPath("spec", "containers").Index(i).Child("securityContext"))...)
} }
if len(errs) > 0 { if len(errs) > 0 {
// ensure psc is not mutated if there are errors
pod.Spec.SecurityContext = originalPSC
pod.Annotations = originalAnnotations
return errs return errs
} }
// if we've reached this code then we've generated and validated an SC for every container in the
// pod so let's apply what we generated. Note: the psc is already applied.
for i, sc := range generatedInitSCs {
pod.Spec.InitContainers[i].SecurityContext = sc
}
for i, sc := range generatedSCs {
pod.Spec.Containers[i].SecurityContext = sc
}
return nil return nil
} }
@ -328,13 +337,13 @@ func buildAttributes(info user.Info, namespace string, policy *extensions.PodSec
// logProviders logs what providers were found for the pod as well as any errors that were encountered // logProviders logs what providers were found for the pod as well as any errors that were encountered
// while creating providers. // while creating providers.
func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { func logProviders(a admission.Attributes, pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) {
for _, err := range providerCreationErrs { for _, err := range providerCreationErrs {
glog.V(4).Infof("provider creation error: %v", err) glog.V(4).Infof("provider creation error: %v", err)
} }
if len(providers) == 0 { if len(providers) == 0 {
glog.V(4).Infof("unable to validate pod %s (generate: %s) against any provider.", pod.Name, pod.GenerateName) glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any provider.", pod.Name, pod.GenerateName, a.GetNamespace())
return return
} }
@ -342,5 +351,5 @@ func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs [
for i, p := range providers { for i, p := range providers {
names[i] = p.GetPSPName() names[i] = p.GetPSPName()
} }
glog.V(4).Infof("validating pod %s (generate: %s) against providers: %s", pod.Name, pod.GenerateName, strings.Join(names, ",")) glog.V(4).Infof("validating pod %s (generate: %s) in namespace %s against providers: %s", pod.Name, pod.GenerateName, a.GetNamespace(), strings.Join(names, ","))
} }

View File

@ -24,6 +24,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
@ -48,7 +50,7 @@ const defaultContainerName = "test-c"
// an authorizer that always returns true. // an authorizer that always returns true.
func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface { func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface {
return &podSecurityPolicyPlugin{ return &podSecurityPolicyPlugin{
Handler: kadmission.NewHandler(kadmission.Create), Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update),
strategyFactory: kpsp.NewSimpleStrategyFactory(), strategyFactory: kpsp.NewSimpleStrategyFactory(),
pspMatcher: getMatchingPolicies, pspMatcher: getMatchingPolicies,
authz: &TestAuthorizer{}, authz: &TestAuthorizer{},
@ -270,6 +272,173 @@ func TestAdmitPrivileged(t *testing.T) {
} }
} }
func defaultPod(t *testing.T, pod *kapi.Pod) *kapi.Pod {
v1Pod := &v1.Pod{}
if err := kapi.Scheme.Convert(pod, v1Pod, nil); err != nil {
t.Fatal(err)
}
kapi.Scheme.Default(v1Pod)
apiPod := &kapi.Pod{}
if err := kapi.Scheme.Convert(v1Pod, apiPod, nil); err != nil {
t.Fatal(err)
}
return apiPod
}
func TestAdmitPreferNonmutating(t *testing.T) {
mutating1 := restrictivePSP()
mutating1.Name = "mutating1"
mutating1.Spec.RunAsUser.Ranges = []extensions.UserIDRange{{Min: int64(1), Max: int64(1)}}
mutating2 := restrictivePSP()
mutating2.Name = "mutating2"
mutating2.Spec.RunAsUser.Ranges = []extensions.UserIDRange{{Min: int64(2), Max: int64(2)}}
privilegedPSP := permissivePSP()
privilegedPSP.Name = "privileged"
unprivilegedRunAsAnyPod := defaultPod(t, &kapi.Pod{
ObjectMeta: metav1.ObjectMeta{},
Spec: kapi.PodSpec{
ServiceAccountName: "default",
Containers: []kapi.Container{{Name: "mycontainer", Image: "myimage"}},
},
})
changedPod := unprivilegedRunAsAnyPod.DeepCopy()
changedPod.Spec.Containers[0].Image = "myimage2"
gcChangedPod := unprivilegedRunAsAnyPod.DeepCopy()
gcChangedPod.OwnerReferences = []metav1.OwnerReference{{Kind: "Foo", Name: "bar"}}
gcChangedPod.Finalizers = []string{"foo"}
tests := map[string]struct {
operation kadmission.Operation
pod *kapi.Pod
oldPod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectMutation bool
expectedPodUser *int64
expectedContainerUser *int64
expectedPSP string
}{
"pod should not be mutated by allow-all strategies": {
operation: kadmission.Create,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{privilegedPSP},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: privilegedPSP.Name,
},
"pod should prefer non-mutating PSP on create": {
operation: kadmission.Create,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: privilegedPSP.Name,
},
"pod should use deterministic mutating PSP on create": {
operation: kadmission.Create,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: true,
expectMutation: true,
expectedPodUser: nil,
expectedContainerUser: &mutating1.Spec.RunAsUser.Ranges[0].Min,
expectedPSP: mutating1.Name,
},
"pod should prefer non-mutating PSP on update": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: changedPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: privilegedPSP.Name,
},
"pod should not allow mutation on update": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: changedPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: false,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: "",
},
"pod should be allowed if completely unchanged on update": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: "",
},
"pod should be allowed if unchanged on update except finalizers,ownerrefs": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: gcChangedPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: "",
},
}
for k, v := range tests {
testPSPAdmitAdvanced(k, v.operation, v.psps, v.pod, v.oldPod, v.shouldPass, v.expectMutation, v.expectedPSP, t)
if v.shouldPass {
actualPodUser := (*int64)(nil)
if v.pod.Spec.SecurityContext != nil {
actualPodUser = v.pod.Spec.SecurityContext.RunAsUser
}
if (actualPodUser == nil) != (v.expectedPodUser == nil) {
t.Errorf("%s expected pod user %v, got %v", k, v.expectedPodUser, actualPodUser)
} else if actualPodUser != nil && *actualPodUser != *v.expectedPodUser {
t.Errorf("%s expected pod user %v, got %v", k, *v.expectedPodUser, *actualPodUser)
}
actualContainerUser := (*int64)(nil)
if v.pod.Spec.Containers[0].SecurityContext != nil {
actualContainerUser = v.pod.Spec.Containers[0].SecurityContext.RunAsUser
}
if (actualContainerUser == nil) != (v.expectedContainerUser == nil) {
t.Errorf("%s expected container user %v, got %v", k, v.expectedContainerUser, actualContainerUser)
} else if actualContainerUser != nil && *actualContainerUser != *v.expectedContainerUser {
t.Errorf("%s expected container user %v, got %v", k, *v.expectedContainerUser, *actualContainerUser)
}
}
}
}
func TestFailClosedOnInvalidPod(t *testing.T) {
plugin := NewTestAdmission(nil)
pod := &v1.Pod{}
attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{})
err := plugin.Admit(attrs)
if err == nil {
t.Fatalf("expected versioned pod object to fail admission")
}
if !strings.Contains(err.Error(), "unexpected type") {
t.Errorf("expected type error but got: %v", err)
}
}
func TestAdmitCaps(t *testing.T) { func TestAdmitCaps(t *testing.T) {
createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod { createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod {
pod := goodPod() pod := goodPod()
@ -693,22 +862,23 @@ func TestAdmitHostIPC(t *testing.T) {
} }
} }
func TestAdmitSELinux(t *testing.T) { func createPodWithSecurityContexts(podSC *kapi.PodSecurityContext, containerSC *kapi.SecurityContext) *kapi.Pod {
createPodWithSELinux := func(opts *kapi.SELinuxOptions) *kapi.Pod {
pod := goodPod() pod := goodPod()
// doesn't matter if we set it here or on the container, the pod.Spec.SecurityContext = podSC
// admission controller uses DetermineEffectiveSC to get the defaulting pod.Spec.Containers[0].SecurityContext = containerSC
// behavior so it can validate what will be applied at runtime
pod.Spec.SecurityContext.SELinuxOptions = opts
return pod return pod
} }
runAsAny := restrictivePSP() func TestAdmitSELinux(t *testing.T) {
runAsAny := permissivePSP()
runAsAny.Name = "runAsAny" runAsAny.Name = "runAsAny"
runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny
runAsAny.Spec.SELinux.SELinuxOptions = nil
mustRunAs := restrictivePSP() mustRunAs := permissivePSP()
mustRunAs.Name = "mustRunAs" mustRunAs.Name = "mustRunAs"
mustRunAs.Spec.SELinux.Rule = extensions.SELinuxStrategyMustRunAs
mustRunAs.Spec.SELinux.SELinuxOptions = &kapi.SELinuxOptions{}
mustRunAs.Spec.SELinux.SELinuxOptions.Level = "level" mustRunAs.Spec.SELinux.SELinuxOptions.Level = "level"
mustRunAs.Spec.SELinux.SELinuxOptions.Role = "role" mustRunAs.Spec.SELinux.SELinuxOptions.Role = "role"
mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type" mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type"
@ -718,40 +888,98 @@ func TestAdmitSELinux(t *testing.T) {
pod *kapi.Pod pod *kapi.Pod
psps []*extensions.PodSecurityPolicy psps []*extensions.PodSecurityPolicy
shouldPass bool shouldPass bool
expectedSELinux *kapi.SELinuxOptions expectedPodSC *kapi.PodSecurityContext
expectedContainerSC *kapi.SecurityContext
expectedPSP string expectedPSP string
}{ }{
"runAsAny with no pod request": { "runAsAny with no request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSELinux: nil, expectedPodSC: nil,
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny with empty pod request": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{},
expectedContainerSC: nil,
expectedPSP: runAsAny.Name,
},
"runAsAny with empty container request": {
pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{}),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: &kapi.SecurityContext{},
expectedPSP: runAsAny.Name,
},
"runAsAny with pod request": { "runAsAny with pod request": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}), pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSELinux: &kapi.SELinuxOptions{User: "foo"}, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny with container request": {
pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
expectedPSP: runAsAny.Name,
},
"runAsAny with pod and container request": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}},
expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
expectedPSP: runAsAny.Name,
},
"mustRunAs with bad pod request": { "mustRunAs with bad pod request": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}), pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false, shouldPass: false,
}, },
"mustRunAs with no pod request": { "mustRunAs with bad container request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false,
},
"mustRunAs with no request": {
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"mustRunAs with good pod request": { "mustRunAs with good pod request": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}), pod: createPodWithSecurityContexts(
&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}},
nil,
),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name,
},
"mustRunAs with good container request": {
pod: createPodWithSecurityContexts(
&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}},
nil,
),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
} }
@ -760,20 +988,11 @@ func TestAdmitSELinux(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass { if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux == nil { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
// ok, don't need to worry about identifying specific diffs t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
continue
} }
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux != nil { if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
t.Errorf("%s expected selinux to be: %v but found nil", k, v.expectedSELinux) t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
continue
}
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions != nil && v.expectedSELinux == nil {
t.Errorf("%s expected selinux to be nil but found: %v", k, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions)
continue
}
if !reflect.DeepEqual(*v.expectedSELinux, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions) {
t.Errorf("%s expected selinux to be: %v but found %v", k, *v.expectedSELinux, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions)
} }
} }
} }
@ -859,84 +1078,138 @@ func TestAdmitAppArmor(t *testing.T) {
} }
func TestAdmitRunAsUser(t *testing.T) { func TestAdmitRunAsUser(t *testing.T) {
createPodWithRunAsUser := func(user int64) *kapi.Pod { podSC := func(user *int64) *kapi.PodSecurityContext {
pod := goodPod() return &kapi.PodSecurityContext{RunAsUser: user}
// doesn't matter if we set it here or on the container, the }
// admission controller uses DetermineEffectiveSC to get the defaulting containerSC := func(user *int64) *kapi.SecurityContext {
// behavior so it can validate what will be applied at runtime return &kapi.SecurityContext{RunAsUser: user}
userID := int64(user)
pod.Spec.SecurityContext.RunAsUser = &userID
return pod
} }
runAsAny := restrictivePSP() runAsAny := permissivePSP()
runAsAny.Name = "runAsAny" runAsAny.Name = "runAsAny"
runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny
mustRunAs := restrictivePSP() mustRunAs := permissivePSP()
mustRunAs.Name = "mustRunAs" mustRunAs.Name = "mustRunAs"
mustRunAs.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAs
mustRunAs.Spec.RunAsUser.Ranges = []extensions.UserIDRange{
{Min: int64(999), Max: int64(1000)},
}
runAsNonRoot := restrictivePSP() runAsNonRoot := permissivePSP()
runAsNonRoot.Name = "runAsNonRoot" runAsNonRoot.Name = "runAsNonRoot"
runAsNonRoot.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot runAsNonRoot.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot
trueValue := true
tests := map[string]struct { tests := map[string]struct {
pod *kapi.Pod pod *kapi.Pod
psps []*extensions.PodSecurityPolicy psps []*extensions.PodSecurityPolicy
shouldPass bool shouldPass bool
expectedRunAsUser *int64 expectedPodSC *kapi.PodSecurityContext
expectedContainerSC *kapi.SecurityContext
expectedPSP string expectedPSP string
}{ }{
"runAsAny no pod request": { "runAsAny no pod request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedRunAsUser: nil, expectedPodSC: nil,
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny pod request": { "runAsAny pod request": {
pod: createPodWithRunAsUser(1), pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedRunAsUser: userIDPtr(1), expectedPodSC: podSC(userIDPtr(1)),
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny container request": {
pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(1))),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: containerSC(userIDPtr(1)),
expectedPSP: runAsAny.Name,
},
"mustRunAs pod request out of range": { "mustRunAs pod request out of range": {
pod: createPodWithRunAsUser(1), pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false, shouldPass: false,
}, },
"mustRunAs container request out of range": {
pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1))),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false,
},
"mustRunAs pod request in range": { "mustRunAs pod request in range": {
pod: createPodWithRunAsUser(999), pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min, expectedPodSC: podSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"mustRunAs no pod request": { "mustRunAs container request in range": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(999))),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min, expectedPodSC: nil,
expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"runAsNonRoot no pod request": { "mustRunAs pod and container request in range": {
pod: goodPod(), pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1000))),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: podSC(userIDPtr(999)),
expectedContainerSC: containerSC(userIDPtr(1000)),
expectedPSP: mustRunAs.Name,
},
"mustRunAs no request": {
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedPSP: mustRunAs.Name,
},
"runAsNonRoot no request": {
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true, shouldPass: true,
expectedRunAsUser: nil, expectedPodSC: nil,
expectedContainerSC: &kapi.SecurityContext{RunAsNonRoot: &trueValue},
expectedPSP: runAsNonRoot.Name, expectedPSP: runAsNonRoot.Name,
}, },
"runAsNonRoot pod request root": { "runAsNonRoot pod request root": {
pod: createPodWithRunAsUser(0), pod: createPodWithSecurityContexts(podSC(userIDPtr(0)), nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: false, shouldPass: false,
}, },
"runAsNonRoot pod request non-root": { "runAsNonRoot pod request non-root": {
pod: createPodWithRunAsUser(1), pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true, shouldPass: true,
expectedRunAsUser: userIDPtr(1), expectedPodSC: podSC(userIDPtr(1)),
expectedPSP: runAsNonRoot.Name,
},
"runAsNonRoot container request root": {
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(0))),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: false,
},
"runAsNonRoot container request non-root": {
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(2))),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true,
expectedPodSC: podSC(userIDPtr(1)),
expectedContainerSC: containerSC(userIDPtr(2)),
expectedPSP: runAsNonRoot.Name, expectedPSP: runAsNonRoot.Name,
}, },
} }
@ -945,81 +1218,82 @@ func TestAdmitRunAsUser(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass { if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser == nil { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
// ok, don't need to worry about identifying specific diffs t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
continue
} }
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser != nil { if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
t.Errorf("%s expected RunAsUser to be: %v but found nil", k, v.expectedRunAsUser) t.Errorf("%s unexpected container sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
continue
}
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser != nil && v.expectedRunAsUser == nil {
t.Errorf("%s expected RunAsUser to be nil but found: %v", k, *v.pod.Spec.Containers[0].SecurityContext.RunAsUser)
continue
}
if *v.expectedRunAsUser != *v.pod.Spec.Containers[0].SecurityContext.RunAsUser {
t.Errorf("%s expected RunAsUser to be: %v but found %v", k, *v.expectedRunAsUser, *v.pod.Spec.Containers[0].SecurityContext.RunAsUser)
} }
} }
} }
} }
func TestAdmitSupplementalGroups(t *testing.T) { func TestAdmitSupplementalGroups(t *testing.T) {
createPodWithSupGroup := func(group int64) *kapi.Pod { podSC := func(group int64) *kapi.PodSecurityContext {
pod := goodPod() return &kapi.PodSecurityContext{SupplementalGroups: []int64{group}}
// doesn't matter if we set it here or on the container, the
// admission controller uses DetermineEffectiveSC to get the defaulting
// behavior so it can validate what will be applied at runtime
groupID := int64(group)
pod.Spec.SecurityContext.SupplementalGroups = []int64{groupID}
return pod
} }
runAsAny := restrictivePSP() runAsAny := permissivePSP()
runAsAny.Name = "runAsAny" runAsAny.Name = "runAsAny"
runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny
mustRunAs := restrictivePSP() mustRunAs := permissivePSP()
mustRunAs.Name = "mustRunAs" mustRunAs.Name = "mustRunAs"
mustRunAs.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyMustRunAs
mustRunAs.Spec.SupplementalGroups.Ranges = []extensions.GroupIDRange{{Min: int64(999), Max: int64(1000)}}
tests := map[string]struct { tests := map[string]struct {
pod *kapi.Pod pod *kapi.Pod
psps []*extensions.PodSecurityPolicy psps []*extensions.PodSecurityPolicy
shouldPass bool shouldPass bool
expectedSupGroups []int64 expectedPodSC *kapi.PodSecurityContext
expectedPSP string expectedPSP string
}{ }{
"runAsAny no pod request": { "runAsAny no pod request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSupGroups: nil, expectedPodSC: nil,
expectedPSP: runAsAny.Name,
},
"runAsAny empty pod request": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{},
expectedPSP: runAsAny.Name,
},
"runAsAny empty pod request empty supplemental groups": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SupplementalGroups: []int64{}}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{}},
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny pod request": { "runAsAny pod request": {
pod: createPodWithSupGroup(1), pod: createPodWithSecurityContexts(podSC(1), nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSupGroups: []int64{1}, expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}},
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"mustRunAs no pod request": { "mustRunAs no pod request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSupGroups: []int64{mustRunAs.Spec.SupplementalGroups.Ranges[0].Min}, expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min),
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"mustRunAs bad pod request": { "mustRunAs bad pod request": {
pod: createPodWithSupGroup(1), pod: createPodWithSecurityContexts(podSC(1), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false, shouldPass: false,
}, },
"mustRunAs good pod request": { "mustRunAs good pod request": {
pod: createPodWithSupGroup(999), pod: createPodWithSecurityContexts(podSC(999), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSupGroups: []int64{999}, expectedPodSC: podSC(999),
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
} }
@ -1028,16 +1302,8 @@ func TestAdmitSupplementalGroups(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass { if v.shouldPass {
if v.pod.Spec.SecurityContext.SupplementalGroups == nil && v.expectedSupGroups != nil { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
t.Errorf("%s expected SupplementalGroups to be: %v but found nil", k, v.expectedSupGroups) t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
continue
}
if v.pod.Spec.SecurityContext.SupplementalGroups != nil && v.expectedSupGroups == nil {
t.Errorf("%s expected SupplementalGroups to be nil but found: %v", k, v.pod.Spec.SecurityContext.SupplementalGroups)
continue
}
if !reflect.DeepEqual(v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups) {
t.Errorf("%s expected SupplementalGroups to be: %v but found %v", k, v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups)
} }
} }
} }
@ -1372,6 +1638,10 @@ func TestAdmitSysctls(t *testing.T) {
} }
func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) { func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) {
testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, pod, nil, shouldPass, true, expectedPSP, t)
}
func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*extensions.PodSecurityPolicy, pod, oldPod *kapi.Pod, shouldPass bool, canMutate bool, expectedPSP string, t *testing.T) {
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore() store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore()
@ -1379,9 +1649,11 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
store.Add(psp) store.Add(psp)
} }
originalPod := pod.DeepCopy()
plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister()) plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister())
attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", op, &user.DefaultInfo{})
err := plugin.Admit(attrs) err := plugin.Admit(attrs)
if shouldPass && err != nil { if shouldPass && err != nil {
@ -1392,6 +1664,18 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP { if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP {
t.Errorf("%s: expected to validate under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation]) t.Errorf("%s: expected to validate under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation])
} }
if !canMutate {
podWithoutPSPAnnotation := pod.DeepCopy()
delete(podWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation)
originalPodWithoutPSPAnnotation := originalPod.DeepCopy()
delete(originalPodWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation)
if !apiequality.Semantic.DeepEqual(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec) {
t.Errorf("%s: expected no mutation, got %s", testCaseName, diff.ObjectGoPrintSideBySide(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec))
}
}
} }
if !shouldPass && err == nil { if !shouldPass && err == nil {
@ -1415,10 +1699,6 @@ func TestAssignSecurityContext(t *testing.T) {
} }
} }
// these are set up such that the containers always have a nil uid. If the case should not
// validate then the uids should not have been updated by the strategy. If the case should
// validate then uids should be set. This is ensuring that we're hanging on to the old SC
// as we generate/validate and only updating the original container if the entire pod validates
testCases := map[string]struct { testCases := map[string]struct {
pod *kapi.Pod pod *kapi.Pod
shouldValidate bool shouldValidate bool
@ -1464,24 +1744,6 @@ func TestAssignSecurityContext(t *testing.T) {
t.Errorf("%s expected validation errors but received none", k) t.Errorf("%s expected validation errors but received none", k)
continue continue
} }
// if we shouldn't have validated ensure that uid is not set on the containers
if !v.shouldValidate {
for _, c := range v.pod.Spec.Containers {
if c.SecurityContext.RunAsUser != nil {
t.Errorf("%s had non-nil UID %d. UID should not be set on test cases that don't validate", k, *c.SecurityContext.RunAsUser)
}
}
}
// if we validated then the pod sc should be updated now with the defaults from the psp
if v.shouldValidate {
for _, c := range v.pod.Spec.Containers {
if *c.SecurityContext.RunAsUser != 999 {
t.Errorf("%s expected uid to be defaulted to 999 but found %v", k, *c.SecurityContext.RunAsUser)
}
}
}
} }
} }
@ -1765,6 +2027,36 @@ func restrictivePSP() *extensions.PodSecurityPolicy {
} }
} }
func permissivePSP() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "privileged",
Annotations: map[string]string{},
},
Spec: extensions.PodSecurityPolicySpec{
AllowPrivilegeEscalation: true,
HostIPC: true,
HostNetwork: true,
HostPID: true,
HostPorts: []extensions.HostPortRange{{Min: 0, Max: 65536}},
Volumes: []extensions.FSType{extensions.All},
AllowedCapabilities: []kapi.Capability{extensions.AllowAllCapabilities},
RunAsUser: extensions.RunAsUserStrategyOptions{
Rule: extensions.RunAsUserStrategyRunAsAny,
},
SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyRunAsAny,
},
FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyRunAsAny,
},
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
Rule: extensions.SupplementalGroupsStrategyRunAsAny,
},
},
}
}
func createNamespaceForTest() *kapi.Namespace { func createNamespaceForTest() *kapi.Namespace {
return &kapi.Namespace{ return &kapi.Namespace{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{