mirror of https://github.com/k3s-io/k3s
PodSecurityPolicy: Order by name, prefer non-mutating policies, require *api.Pod, allow GC updates
parent
a5f722e181
commit
8c5b01376a
|
@ -16,12 +16,12 @@ go_library(
|
|||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/extensions/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//pkg/registry/rbac:go_default_library",
|
||||
"//pkg/security/podsecuritypolicy:go_default_library",
|
||||
"//pkg/security/podsecuritypolicy/util:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//pkg/util/maps: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/util/validation/field: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/util: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/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
|
|
|
@ -19,10 +19,12 @@ package podsecuritypolicy
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
|
@ -34,11 +36,10 @@ import (
|
|||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
|
||||
psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
|
||||
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
|
||||
sc "k8s.io/kubernetes/pkg/securitycontext"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
"k8s.io/kubernetes/pkg/util/maps"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -120,8 +121,17 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
|
|||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -143,32 +153,70 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
|
|||
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)
|
||||
logProviders(pod, providers, errs)
|
||||
logProviders(a, pod, providers, errs)
|
||||
|
||||
if len(providers) == 0 {
|
||||
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
|
||||
validationErrs := field.ErrorList{}
|
||||
var (
|
||||
allowedPod *api.Pod
|
||||
allowingProvider psp.Provider
|
||||
)
|
||||
|
||||
loop:
|
||||
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...)
|
||||
continue
|
||||
}
|
||||
|
||||
// the entire pod validated, annotate and accept the pod
|
||||
glog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, provider.GetPSPName())
|
||||
// the entire pod validated
|
||||
|
||||
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 {
|
||||
pod.ObjectMeta.Annotations = map[string]string{}
|
||||
}
|
||||
pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.GetPSPName()
|
||||
pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = allowingProvider.GetPSPName()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// the same psp or is not considered valid.
|
||||
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{}
|
||||
|
||||
psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod)
|
||||
if err != nil {
|
||||
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
|
||||
originalAnnotations := maps.CopySS(pod.Annotations)
|
||||
pod.Annotations = pscAnnotations
|
||||
|
||||
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
|
||||
// as all containers validated under the same PSP.
|
||||
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)
|
||||
for i := range pod.Spec.InitContainers {
|
||||
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.InitContainers[i])
|
||||
if err != nil {
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error()))
|
||||
continue
|
||||
}
|
||||
generatedInitSCs = append(generatedInitSCs, sc)
|
||||
|
||||
containerCopy.SecurityContext = sc
|
||||
pod.Spec.InitContainers[i].SecurityContext = sc
|
||||
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
|
||||
// as all containers validated under the same PSP.
|
||||
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)
|
||||
for i := range pod.Spec.Containers {
|
||||
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[i])
|
||||
if err != nil {
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error()))
|
||||
continue
|
||||
}
|
||||
generatedSCs[i] = sc
|
||||
|
||||
containerCopy.SecurityContext = sc
|
||||
pod.Spec.Containers[i].SecurityContext = sc
|
||||
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 {
|
||||
// ensure psc is not mutated if there are errors
|
||||
pod.Spec.SecurityContext = originalPSC
|
||||
pod.Annotations = originalAnnotations
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// 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 {
|
||||
glog.V(4).Infof("provider creation error: %v", err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -342,5 +351,5 @@ func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs [
|
|||
for i, p := range providers {
|
||||
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, ","))
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import (
|
|||
|
||||
"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"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
@ -48,7 +50,7 @@ const defaultContainerName = "test-c"
|
|||
// an authorizer that always returns true.
|
||||
func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface {
|
||||
return &podSecurityPolicyPlugin{
|
||||
Handler: kadmission.NewHandler(kadmission.Create),
|
||||
Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update),
|
||||
strategyFactory: kpsp.NewSimpleStrategyFactory(),
|
||||
pspMatcher: getMatchingPolicies,
|
||||
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) {
|
||||
createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod {
|
||||
pod := goodPod()
|
||||
|
@ -693,22 +862,23 @@ func TestAdmitHostIPC(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdmitSELinux(t *testing.T) {
|
||||
createPodWithSELinux := func(opts *kapi.SELinuxOptions) *kapi.Pod {
|
||||
func createPodWithSecurityContexts(podSC *kapi.PodSecurityContext, containerSC *kapi.SecurityContext) *kapi.Pod {
|
||||
pod := goodPod()
|
||||
// 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
|
||||
pod.Spec.SecurityContext.SELinuxOptions = opts
|
||||
pod.Spec.SecurityContext = podSC
|
||||
pod.Spec.Containers[0].SecurityContext = containerSC
|
||||
return pod
|
||||
}
|
||||
|
||||
runAsAny := restrictivePSP()
|
||||
func TestAdmitSELinux(t *testing.T) {
|
||||
runAsAny := permissivePSP()
|
||||
runAsAny.Name = "runAsAny"
|
||||
runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny
|
||||
runAsAny.Spec.SELinux.SELinuxOptions = nil
|
||||
|
||||
mustRunAs := restrictivePSP()
|
||||
mustRunAs := permissivePSP()
|
||||
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.Role = "role"
|
||||
mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type"
|
||||
|
@ -718,40 +888,98 @@ func TestAdmitSELinux(t *testing.T) {
|
|||
pod *kapi.Pod
|
||||
psps []*extensions.PodSecurityPolicy
|
||||
shouldPass bool
|
||||
expectedSELinux *kapi.SELinuxOptions
|
||||
expectedPodSC *kapi.PodSecurityContext
|
||||
expectedContainerSC *kapi.SecurityContext
|
||||
expectedPSP string
|
||||
}{
|
||||
"runAsAny with no pod request": {
|
||||
pod: goodPod(),
|
||||
"runAsAny with no request": {
|
||||
pod: createPodWithSecurityContexts(nil, nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsAny},
|
||||
shouldPass: true,
|
||||
expectedSELinux: nil,
|
||||
expectedPodSC: nil,
|
||||
expectedContainerSC: nil,
|
||||
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": {
|
||||
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}),
|
||||
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsAny},
|
||||
shouldPass: true,
|
||||
expectedSELinux: &kapi.SELinuxOptions{User: "foo"},
|
||||
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
|
||||
expectedContainerSC: nil,
|
||||
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": {
|
||||
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}),
|
||||
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
|
||||
psps: []*extensions.PodSecurityPolicy{mustRunAs},
|
||||
shouldPass: false,
|
||||
},
|
||||
"mustRunAs with no pod request": {
|
||||
pod: goodPod(),
|
||||
"mustRunAs with bad container request": {
|
||||
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},
|
||||
shouldPass: true,
|
||||
expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions,
|
||||
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
|
||||
expectedContainerSC: nil,
|
||||
expectedPSP: mustRunAs.Name,
|
||||
},
|
||||
"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},
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
@ -760,20 +988,11 @@ func TestAdmitSELinux(t *testing.T) {
|
|||
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
|
||||
|
||||
if v.shouldPass {
|
||||
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux == nil {
|
||||
// ok, don't need to worry about identifying specific diffs
|
||||
continue
|
||||
if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
|
||||
t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
|
||||
}
|
||||
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux != nil {
|
||||
t.Errorf("%s expected selinux to be: %v but found nil", k, v.expectedSELinux)
|
||||
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)
|
||||
if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
|
||||
t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -859,84 +1078,138 @@ func TestAdmitAppArmor(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAdmitRunAsUser(t *testing.T) {
|
||||
createPodWithRunAsUser := func(user int64) *kapi.Pod {
|
||||
pod := goodPod()
|
||||
// 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
|
||||
userID := int64(user)
|
||||
pod.Spec.SecurityContext.RunAsUser = &userID
|
||||
return pod
|
||||
podSC := func(user *int64) *kapi.PodSecurityContext {
|
||||
return &kapi.PodSecurityContext{RunAsUser: user}
|
||||
}
|
||||
containerSC := func(user *int64) *kapi.SecurityContext {
|
||||
return &kapi.SecurityContext{RunAsUser: user}
|
||||
}
|
||||
|
||||
runAsAny := restrictivePSP()
|
||||
runAsAny := permissivePSP()
|
||||
runAsAny.Name = "runAsAny"
|
||||
runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny
|
||||
|
||||
mustRunAs := restrictivePSP()
|
||||
mustRunAs := permissivePSP()
|
||||
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.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot
|
||||
|
||||
trueValue := true
|
||||
|
||||
tests := map[string]struct {
|
||||
pod *kapi.Pod
|
||||
psps []*extensions.PodSecurityPolicy
|
||||
shouldPass bool
|
||||
expectedRunAsUser *int64
|
||||
expectedPodSC *kapi.PodSecurityContext
|
||||
expectedContainerSC *kapi.SecurityContext
|
||||
expectedPSP string
|
||||
}{
|
||||
"runAsAny no pod request": {
|
||||
pod: goodPod(),
|
||||
pod: createPodWithSecurityContexts(nil, nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsAny},
|
||||
shouldPass: true,
|
||||
expectedRunAsUser: nil,
|
||||
expectedPodSC: nil,
|
||||
expectedContainerSC: nil,
|
||||
expectedPSP: runAsAny.Name,
|
||||
},
|
||||
"runAsAny pod request": {
|
||||
pod: createPodWithRunAsUser(1),
|
||||
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsAny},
|
||||
shouldPass: true,
|
||||
expectedRunAsUser: userIDPtr(1),
|
||||
expectedPodSC: podSC(userIDPtr(1)),
|
||||
expectedContainerSC: nil,
|
||||
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": {
|
||||
pod: createPodWithRunAsUser(1),
|
||||
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{mustRunAs},
|
||||
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": {
|
||||
pod: createPodWithRunAsUser(999),
|
||||
pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{mustRunAs},
|
||||
shouldPass: true,
|
||||
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min,
|
||||
expectedPodSC: podSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
|
||||
expectedContainerSC: nil,
|
||||
expectedPSP: mustRunAs.Name,
|
||||
},
|
||||
"mustRunAs no pod request": {
|
||||
pod: goodPod(),
|
||||
"mustRunAs container request in range": {
|
||||
pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(999))),
|
||||
psps: []*extensions.PodSecurityPolicy{mustRunAs},
|
||||
shouldPass: true,
|
||||
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min,
|
||||
expectedPodSC: nil,
|
||||
expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
|
||||
expectedPSP: mustRunAs.Name,
|
||||
},
|
||||
"runAsNonRoot no pod request": {
|
||||
pod: goodPod(),
|
||||
"mustRunAs pod and container request in range": {
|
||||
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},
|
||||
shouldPass: true,
|
||||
expectedRunAsUser: nil,
|
||||
expectedPodSC: nil,
|
||||
expectedContainerSC: &kapi.SecurityContext{RunAsNonRoot: &trueValue},
|
||||
expectedPSP: runAsNonRoot.Name,
|
||||
},
|
||||
"runAsNonRoot pod request root": {
|
||||
pod: createPodWithRunAsUser(0),
|
||||
pod: createPodWithSecurityContexts(podSC(userIDPtr(0)), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
|
||||
shouldPass: false,
|
||||
},
|
||||
"runAsNonRoot pod request non-root": {
|
||||
pod: createPodWithRunAsUser(1),
|
||||
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
@ -945,81 +1218,82 @@ func TestAdmitRunAsUser(t *testing.T) {
|
|||
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
|
||||
|
||||
if v.shouldPass {
|
||||
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser == nil {
|
||||
// ok, don't need to worry about identifying specific diffs
|
||||
continue
|
||||
if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
|
||||
t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
|
||||
}
|
||||
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser != nil {
|
||||
t.Errorf("%s expected RunAsUser to be: %v but found nil", k, v.expectedRunAsUser)
|
||||
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)
|
||||
if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
|
||||
t.Errorf("%s unexpected container sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitSupplementalGroups(t *testing.T) {
|
||||
createPodWithSupGroup := func(group int64) *kapi.Pod {
|
||||
pod := goodPod()
|
||||
// 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
|
||||
podSC := func(group int64) *kapi.PodSecurityContext {
|
||||
return &kapi.PodSecurityContext{SupplementalGroups: []int64{group}}
|
||||
}
|
||||
|
||||
runAsAny := restrictivePSP()
|
||||
runAsAny := permissivePSP()
|
||||
runAsAny.Name = "runAsAny"
|
||||
runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny
|
||||
|
||||
mustRunAs := restrictivePSP()
|
||||
mustRunAs := permissivePSP()
|
||||
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 {
|
||||
pod *kapi.Pod
|
||||
psps []*extensions.PodSecurityPolicy
|
||||
shouldPass bool
|
||||
expectedSupGroups []int64
|
||||
expectedPodSC *kapi.PodSecurityContext
|
||||
expectedPSP string
|
||||
}{
|
||||
"runAsAny no pod request": {
|
||||
pod: goodPod(),
|
||||
pod: createPodWithSecurityContexts(nil, nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsAny},
|
||||
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,
|
||||
},
|
||||
"runAsAny pod request": {
|
||||
pod: createPodWithSupGroup(1),
|
||||
pod: createPodWithSecurityContexts(podSC(1), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{runAsAny},
|
||||
shouldPass: true,
|
||||
expectedSupGroups: []int64{1},
|
||||
expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}},
|
||||
expectedPSP: runAsAny.Name,
|
||||
},
|
||||
"mustRunAs no pod request": {
|
||||
pod: goodPod(),
|
||||
pod: createPodWithSecurityContexts(nil, nil),
|
||||
psps: []*extensions.PodSecurityPolicy{mustRunAs},
|
||||
shouldPass: true,
|
||||
expectedSupGroups: []int64{mustRunAs.Spec.SupplementalGroups.Ranges[0].Min},
|
||||
expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min),
|
||||
expectedPSP: mustRunAs.Name,
|
||||
},
|
||||
"mustRunAs bad pod request": {
|
||||
pod: createPodWithSupGroup(1),
|
||||
pod: createPodWithSecurityContexts(podSC(1), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{mustRunAs},
|
||||
shouldPass: false,
|
||||
},
|
||||
"mustRunAs good pod request": {
|
||||
pod: createPodWithSupGroup(999),
|
||||
pod: createPodWithSecurityContexts(podSC(999), nil),
|
||||
psps: []*extensions.PodSecurityPolicy{mustRunAs},
|
||||
shouldPass: true,
|
||||
expectedSupGroups: []int64{999},
|
||||
expectedPodSC: podSC(999),
|
||||
expectedPSP: mustRunAs.Name,
|
||||
},
|
||||
}
|
||||
|
@ -1028,16 +1302,8 @@ func TestAdmitSupplementalGroups(t *testing.T) {
|
|||
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
|
||||
|
||||
if v.shouldPass {
|
||||
if v.pod.Spec.SecurityContext.SupplementalGroups == nil && v.expectedSupGroups != nil {
|
||||
t.Errorf("%s expected SupplementalGroups to be: %v but found nil", k, v.expectedSupGroups)
|
||||
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)
|
||||
if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
|
||||
t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
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())
|
||||
store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore()
|
||||
|
||||
|
@ -1379,9 +1649,11 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
|
|||
store.Add(psp)
|
||||
}
|
||||
|
||||
originalPod := pod.DeepCopy()
|
||||
|
||||
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)
|
||||
|
||||
if shouldPass && err != nil {
|
||||
|
@ -1392,6 +1664,18 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
|
|||
if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP {
|
||||
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 {
|
||||
|
@ -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 {
|
||||
pod *kapi.Pod
|
||||
shouldValidate bool
|
||||
|
@ -1464,24 +1744,6 @@ func TestAssignSecurityContext(t *testing.T) {
|
|||
t.Errorf("%s expected validation errors but received none", k)
|
||||
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 {
|
||||
return &kapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
|
Loading…
Reference in New Issue