From 967323558387e88bf3df532610511ed06d9dbfa7 Mon Sep 17 00:00:00 2001 From: Tim Allclair Date: Mon, 13 Nov 2017 17:03:24 -0800 Subject: [PATCH] Optimize PSP authorization --- .../security/podsecuritypolicy/BUILD | 3 +- .../security/podsecuritypolicy/admission.go | 107 +++----- .../podsecuritypolicy/admission_test.go | 241 ++++++++++++------ 3 files changed, 204 insertions(+), 147 deletions(-) diff --git a/plugin/pkg/admission/security/podsecuritypolicy/BUILD b/plugin/pkg/admission/security/podsecuritypolicy/BUILD index ceceed20e24..a2f5467e019 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/BUILD +++ b/plugin/pkg/admission/security/podsecuritypolicy/BUILD @@ -42,7 +42,6 @@ go_test( "//pkg/apis/core/helper:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", - "//pkg/client/listers/extensions/internalversion:go_default_library", "//pkg/controller:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/security/podsecuritypolicy:go_default_library", @@ -53,8 +52,8 @@ go_test( "//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", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", ], diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission.go b/plugin/pkg/admission/security/podsecuritypolicy/admission.go index fa6f62e36e7..15b94df75a0 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission.go +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission.go @@ -49,19 +49,15 @@ const ( // Register registers a plugin func Register(plugins *admission.Plugins) { plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { - plugin := NewPlugin(psp.NewSimpleStrategyFactory(), getMatchingPolicies, true) + plugin := newPlugin(psp.NewSimpleStrategyFactory(), true) return plugin, nil }) } -// PSPMatchFn allows plugging in how PSPs are matched against user information. -type PSPMatchFn func(lister extensionslisters.PodSecurityPolicyLister, user user.Info, sa user.Info, authz authorizer.Authorizer, namespace string) ([]*extensions.PodSecurityPolicy, error) - // PodSecurityPolicyPlugin holds state for and implements the admission plugin. type PodSecurityPolicyPlugin struct { *admission.Handler strategyFactory psp.StrategyFactory - pspMatcher PSPMatchFn failOnNoPolicies bool authz authorizer.Authorizer lister extensionslisters.PodSecurityPolicyLister @@ -88,12 +84,11 @@ var _ admission.ValidationInterface = &PodSecurityPolicyPlugin{} var _ genericadmissioninit.WantsAuthorizer = &PodSecurityPolicyPlugin{} var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &PodSecurityPolicyPlugin{} -// NewPlugin creates a new PSP admission plugin. -func NewPlugin(strategyFactory psp.StrategyFactory, pspMatcher PSPMatchFn, failOnNoPolicies bool) *PodSecurityPolicyPlugin { +// newPlugin creates a new PSP admission plugin. +func newPlugin(strategyFactory psp.StrategyFactory, failOnNoPolicies bool) *PodSecurityPolicyPlugin { return &PodSecurityPolicyPlugin{ Handler: admission.NewHandler(admission.Create, admission.Update), strategyFactory: strategyFactory, - pspMatcher: pspMatcher, failOnNoPolicies: failOnNoPolicies, } } @@ -207,49 +202,60 @@ func (c *PodSecurityPolicyPlugin) computeSecurityContext(a admission.Attributes, saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") } - matchedPolicies, err := c.pspMatcher(c.lister, a.GetUserInfo(), saInfo, c.authz, a.GetNamespace()) + policies, err := c.lister.List(labels.Everything()) if err != nil { return nil, "", nil, err } // if we have no policies and want to succeed then return. Otherwise we'll end up with no // providers and fail with "unable to validate against any pod security policy" below. - if len(matchedPolicies) == 0 && !c.failOnNoPolicies { + if len(policies) == 0 && !c.failOnNoPolicies { return pod, "", nil, 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 + sort.SliceStable(policies, func(i, j int) bool { + return strings.Compare(policies[i].Name, policies[j].Name) < 0 }) - providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) - logProviders(a, pod, providers, errs) + providers, errs := c.createProvidersFromPolicies(policies, pod.Namespace) + for _, err := range errs { + glog.V(4).Infof("provider creation error: %v", err) + } if len(providers) == 0 { return nil, "", nil, fmt.Errorf("no providers available to validate pod request") } - // all containers in a single pod must validate under a single provider or we will reject the request - validationErrs := field.ErrorList{} var ( allowedMutatedPod *api.Pod allowingMutatingPSP string + // Map of PSP name to associated validation errors. + validationErrs = map[string]field.ErrorList{} ) for _, provider := range providers { podCopy := pod.DeepCopy() if errs := assignSecurityContext(provider, podCopy, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 { - validationErrs = append(validationErrs, errs...) + validationErrs[provider.GetPSPName()] = errs continue } // the entire pod validated + mutated := !apiequality.Semantic.DeepEqual(pod, podCopy) + if mutated && !specMutationAllowed { + continue + } + + if !isAuthorizedForPolicy(a.GetUserInfo(), saInfo, a.GetNamespace(), provider.GetPSPName(), c.authz) { + continue + } + switch { - case apiequality.Semantic.DeepEqual(pod, podCopy): + case !mutated: // if it validated without mutating anything, use this result return podCopy, provider.GetPSPName(), nil, nil @@ -261,11 +267,18 @@ func (c *PodSecurityPolicyPlugin) computeSecurityContext(a admission.Attributes, } } - if allowedMutatedPod == nil { - return nil, "", validationErrs, nil + if allowedMutatedPod != nil { + return allowedMutatedPod, allowingMutatingPSP, nil, nil } - return allowedMutatedPod, allowingMutatingPSP, nil, nil + // Pod is rejected. Filter the validation errors to only include errors from authorized PSPs. + aggregate := field.ErrorList{} + for psp, errs := range validationErrs { + if isAuthorizedForPolicy(a.GetUserInfo(), saInfo, a.GetNamespace(), psp, c.authz) { + aggregate = append(aggregate, errs...) + } + } + return nil, "", aggregate, nil } // assignSecurityContext creates a security context for each container in the pod @@ -332,35 +345,18 @@ func (c *PodSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions return providers, errs } -// getMatchingPolicies returns policies from the lister. For now this returns everything -// in the future it can filter based on UserInfo and permissions. -// -// TODO: this will likely need optimization since the initial implementation will -// always query for authorization. Needs scale testing and possibly checking against -// a cache. -func getMatchingPolicies(lister extensionslisters.PodSecurityPolicyLister, user user.Info, sa user.Info, authz authorizer.Authorizer, namespace string) ([]*extensions.PodSecurityPolicy, error) { - matchedPolicies := make([]*extensions.PodSecurityPolicy, 0) - - list, err := lister.List(labels.Everything()) - if err != nil { - return nil, err - } - - for _, constraint := range list { - if authorizedForPolicy(user, namespace, constraint, authz) || authorizedForPolicy(sa, namespace, constraint, authz) { - matchedPolicies = append(matchedPolicies, constraint) - } - } - - return matchedPolicies, nil +func isAuthorizedForPolicy(user, sa user.Info, namespace, policyName string, authz authorizer.Authorizer) bool { + // Check the service account first, as that is the more common use case. + return authorizedForPolicy(sa, namespace, policyName, authz) || + authorizedForPolicy(user, namespace, policyName, authz) } // authorizedForPolicy returns true if info is authorized to perform the "use" verb on the policy resource. -func authorizedForPolicy(info user.Info, namespace string, policy *extensions.PodSecurityPolicy, authz authorizer.Authorizer) bool { +func authorizedForPolicy(info user.Info, namespace string, policyName string, authz authorizer.Authorizer) bool { if info == nil { return false } - attr := buildAttributes(info, namespace, policy) + attr := buildAttributes(info, namespace, policyName) decision, reason, err := authz.Authorize(attr) if err != nil { glog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err) @@ -369,35 +365,16 @@ func authorizedForPolicy(info user.Info, namespace string, policy *extensions.Po } // buildAttributes builds an attributes record for a SAR based on the user info and policy. -func buildAttributes(info user.Info, namespace string, policy *extensions.PodSecurityPolicy) authorizer.Attributes { +func buildAttributes(info user.Info, namespace string, policyName string) authorizer.Attributes { // check against the namespace that the pod is being created in to allow per-namespace PSP grants. attr := authorizer.AttributesRecord{ User: info, Verb: "use", Namespace: namespace, - Name: policy.Name, + Name: policyName, APIGroup: extensions.GroupName, Resource: "podsecuritypolicies", ResourceRequest: true, } return attr } - -// logProviders logs what providers were found for the pod as well as any errors that were encountered -// while creating providers. -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) in namespace %s against any provider.", pod.Name, pod.GenerateName, a.GetNamespace()) - return - } - - names := make([]string, len(providers)) - for i, p := range providers { - names[i] = p.GetPSPName() - } - glog.V(4).Infof("validating pod %s (generate: %s) in namespace %s against providers: %s", pod.Name, pod.GenerateName, a.GetNamespace(), strings.Join(names, ",")) -} diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go index bf043b75232..2b99c0b4c29 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go @@ -28,8 +28,8 @@ import ( 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" kadmission "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -37,7 +37,6 @@ import ( "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/extensions" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" - extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/security/apparmor" kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" @@ -47,14 +46,21 @@ import ( const defaultContainerName = "test-c" -// NewTestAdmission provides an admission plugin with test implementations of internal structs. It uses -// an authorizer that always returns true. -func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) *PodSecurityPolicyPlugin { +// NewTestAdmission provides an admission plugin with test implementations of internal structs. +func NewTestAdmission(psps []*extensions.PodSecurityPolicy, authz authorizer.Authorizer) *PodSecurityPolicyPlugin { + informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) + store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore() + for _, psp := range psps { + store.Add(psp) + } + lister := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister() + if authz == nil { + authz = &TestAuthorizer{} + } return &PodSecurityPolicyPlugin{ Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), strategyFactory: kpsp.NewSimpleStrategyFactory(), - pspMatcher: getMatchingPolicies, - authz: &TestAuthorizer{}, + authz: authz, lister: lister, } } @@ -434,7 +440,7 @@ func TestAdmitPreferNonmutating(t *testing.T) { } for k, v := range tests { - testPSPAdmitAdvanced(k, v.operation, v.psps, v.pod, v.podBeforeUpdate, v.shouldPassAdmit, v.shouldPassValidate, v.expectMutation, v.expectedPSP, t) + testPSPAdmitAdvanced(k, v.operation, v.psps, nil, &user.DefaultInfo{}, v.pod, v.podBeforeUpdate, v.shouldPassAdmit, v.shouldPassValidate, v.expectMutation, v.expectedPSP, t) if v.shouldPassAdmit { actualPodUser := (*int64)(nil) @@ -461,7 +467,7 @@ func TestAdmitPreferNonmutating(t *testing.T) { } func TestFailClosedOnInvalidPod(t *testing.T) { - plugin := NewTestAdmission(nil) + plugin := NewTestAdmission(nil, 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{}) @@ -1785,22 +1791,14 @@ func TestAdmitSysctls(t *testing.T) { } func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPassAdmit, shouldPassValidate bool, expectedPSP string, t *testing.T) { - testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, pod, nil, shouldPassAdmit, shouldPassValidate, true, expectedPSP, t) + testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, nil, &user.DefaultInfo{}, pod, nil, shouldPassAdmit, shouldPassValidate, true, expectedPSP, t) } -func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*extensions.PodSecurityPolicy, pod, oldPod *kapi.Pod, shouldPassAdmit, shouldPassValidate bool, canMutate bool, expectedPSP string, t *testing.T) { - informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) - store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore() - - for _, psp := range psps { - store.Add(psp) - } - +func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*extensions.PodSecurityPolicy, authz authorizer.Authorizer, userInfo user.Info, pod, oldPod *kapi.Pod, shouldPassAdmit, shouldPassValidate bool, canMutate bool, expectedPSP string, t *testing.T) { originalPod := pod.DeepCopy() + plugin := NewTestAdmission(psps, authz) - plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister()) - - attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", op, &user.DefaultInfo{}) + attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, "", kapi.Resource("pods").WithVersion("version"), "", op, userInfo) err := plugin.Admit(attrs) if shouldPassAdmit && err != nil { @@ -1987,59 +1985,59 @@ func TestCreateProvidersFromConstraints(t *testing.T) { } } -func TestGetMatchingPolicies(t *testing.T) { +func TestPolicyAuthorization(t *testing.T) { policyWithName := func(name string) *extensions.PodSecurityPolicy { - p := restrictivePSP() + p := permissivePSP() p.Name = name return p } tests := map[string]struct { - user user.Info - sa user.Info - ns string - expectedPolicies sets.String - inPolicies []*extensions.PodSecurityPolicy - allowed map[string]map[string]map[string]bool + user user.Info + sa string + ns string + expectedPolicy string + inPolicies []*extensions.PodSecurityPolicy + allowed map[string]map[string]map[string]bool }{ "policy allowed by user": { user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ "user": { "test": {"policy": true}, }, }, - inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicies: sets.NewString("policy"), + inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, + expectedPolicy: "policy", }, "policy allowed by sa": { user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy": true}, }, }, - inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicies: sets.NewString("policy"), + inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, + expectedPolicy: "policy", }, "no policies allowed": { - user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, - ns: "test", - allowed: map[string]map[string]map[string]bool{}, - inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicies: sets.NewString(), + user: &user.DefaultInfo{Name: "user"}, + sa: "sa", + ns: "test", + allowed: map[string]map[string]map[string]bool{}, + inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, + expectedPolicy: "", }, "multiple policies allowed": { user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, "": {"policy4": true}, "other": {"policy6": true}, @@ -2051,22 +2049,23 @@ func TestGetMatchingPolicies(t *testing.T) { }, }, inPolicies: []*extensions.PodSecurityPolicy{ - policyWithName("policy1"), // allowed by sa - policyWithName("policy2"), // allowed by user - policyWithName("policy3"), // not allowed - policyWithName("policy4"), // allowed by sa at cluster level - policyWithName("policy5"), // allowed by user at cluster level - policyWithName("policy6"), // not allowed in this namespace - policyWithName("policy7"), // not allowed in this namespace + // Prefix to force checking these policies first. + policyWithName("a_policy1"), // not allowed in this namespace + policyWithName("a_policy2"), // not allowed in this namespace + policyWithName("policy2"), // allowed by sa + policyWithName("policy3"), // allowed by user + policyWithName("policy4"), // not allowed + policyWithName("policy5"), // allowed by sa at cluster level + policyWithName("policy6"), // allowed by user at cluster level }, - expectedPolicies: sets.NewString("policy1", "policy2", "policy4", "policy5"), + expectedPolicy: "policy2", }, "policies are not allowed for nil user info": { user: nil, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, }, "user": { @@ -2079,14 +2078,14 @@ func TestGetMatchingPolicies(t *testing.T) { policyWithName("policy3"), }, // only the policies for the sa are allowed when user info is nil - expectedPolicies: sets.NewString("policy1"), + expectedPolicy: "policy1", }, "policies are not allowed for nil sa info": { user: &user.DefaultInfo{Name: "user"}, - sa: nil, + sa: "", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, }, "user": { @@ -2099,14 +2098,14 @@ func TestGetMatchingPolicies(t *testing.T) { policyWithName("policy3"), }, // only the policies for the user are allowed when sa info is nil - expectedPolicies: sets.NewString("policy2"), + expectedPolicy: "policy2", }, "policies are not allowed for nil sa and user info": { user: nil, - sa: nil, + sa: "", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, }, "user": { @@ -2119,30 +2118,110 @@ func TestGetMatchingPolicies(t *testing.T) { policyWithName("policy3"), }, // no policies are allowed if sa and user are both nil - expectedPolicies: sets.NewString(), + expectedPolicy: "", }, } for k, v := range tests { - informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) - pspInformer := informerFactory.Extensions().InternalVersion().PodSecurityPolicies() - store := pspInformer.Informer().GetStore() - for _, psp := range v.inPolicies { - store.Add(psp) - } + var ( + oldPod *kapi.Pod + shouldPass = v.expectedPolicy != "" + authz = &TestAuthorizer{usernameToNamespaceToAllowedPSPs: v.allowed} + canMutate = true + ) + pod := goodPod() + pod.Namespace = v.ns + pod.Spec.ServiceAccountName = v.sa + testPSPAdmitAdvanced(k, kadmission.Create, v.inPolicies, authz, v.user, + pod, oldPod, shouldPass, shouldPass, canMutate, v.expectedPolicy, t) + } +} - authz := &TestAuthorizer{usernameToNamespaceToAllowedPSPs: v.allowed} - allowedPolicies, err := getMatchingPolicies(pspInformer.Lister(), v.user, v.sa, authz, v.ns) - if err != nil { - t.Errorf("%s got unexpected error %#v", k, err) - continue - } - allowedPolicyNames := sets.NewString() - for _, p := range allowedPolicies { - allowedPolicyNames.Insert(p.Name) - } - if !v.expectedPolicies.Equal(allowedPolicyNames) { - t.Errorf("%s received unexpected policies. Expected %#v but got %#v", k, v.expectedPolicies.List(), allowedPolicyNames.List()) - } +func TestPolicyAuthorizationErrors(t *testing.T) { + policyWithName := func(name string) *extensions.PodSecurityPolicy { + p := restrictivePSP() + p.Name = name + return p + } + + const ( + sa = "sa" + ns = "test" + userName = "user" + ) + + tests := map[string]struct { + priviliged bool + inPolicies []*extensions.PodSecurityPolicy + allowed map[string]map[string]map[string]bool + expectValidationErrs int + }{ + "policies not allowed": { + allowed: map[string]map[string]map[string]bool{}, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 0, + }, + "policy allowed by user": { + allowed: map[string]map[string]map[string]bool{ + "user": { + "test": {"policy1": true}, + }, + }, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 1, + }, + "policy allowed by service account": { + allowed: map[string]map[string]map[string]bool{ + serviceaccount.MakeUsername("test", "sa"): { + "test": {"policy2": true}, + }, + }, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 1, + }, + "multiple policies allowed": { + allowed: map[string]map[string]map[string]bool{ + "user": { + "test": {"policy1": true}, + }, + serviceaccount.MakeUsername("test", "sa"): { + "test": {"policy2": true}, + }, + }, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 2, + }, + } + for desc, tc := range tests { + t.Run(desc, func(t *testing.T) { + var ( + authz = &TestAuthorizer{usernameToNamespaceToAllowedPSPs: tc.allowed} + privileged = true + ) + pod := goodPod() + pod.Namespace = ns + pod.Spec.ServiceAccountName = sa + pod.Spec.Containers[0].SecurityContext.Privileged = &privileged + + plugin := NewTestAdmission(tc.inPolicies, authz) + attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), ns, "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{Name: userName}) + + allowedPod, _, validationErrs, err := plugin.computeSecurityContext(attrs, pod, true) + assert.Nil(t, allowedPod) + assert.NoError(t, err) + assert.Len(t, validationErrs, tc.expectValidationErrs) + }) } } @@ -2217,6 +2296,8 @@ func permissivePSP() *extensions.PodSecurityPolicy { func goodPod() *kapi.Pod { return &kapi.Pod{ ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "namespace", Annotations: map[string]string{}, }, Spec: kapi.PodSpec{