Merge pull request #55643 from tallclair/psp-scale

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

[PodSecurityPolicy] Optimize authorization check

**What this PR does / why we need it**:

Authorizing PodSecurityPolicy use may involve a remote call, and can be slow. Rather than authorizing the user / SA for every policy in the cluster, only test authz for the policies under which the pod is valid.

This is a big improvement in the case where there are a lot of policies for which the pod is not valid (benchmark below), but should also help when the pod is valid under other policies, as it allows the authorization to short-circuit on the first accepted policy.

**Benchmark:**
Highlight from scale testing (see https://docs.google.com/document/d/1IIcHHE_No1KMAybW5krIphdN325eGa2sxF2eqg2YAPI/edit for the full results). These were run with 1000 policies under which the pods were not valid, and had no role bindings.

| | method | resource | 50th percentile | 90th percentile | 99th percentile
| -- | -- | -- | -- | -- | --
| 1.8 HEAD | POST | pods | 8.696784s | 20.497659s | 22.472421s
| 1.8 With fix | POST | pods | 25.454ms | 29.068ms | 85.817ms

(I didn't benchmark master, but expect the difference to be more drastic, since the authorization is run twice - for both Admit and Validate)

**Which issue(s) this PR fixes**:
Fixes #55521

**Special notes for your reviewer**:
The validation errors are no longer totally accurate, as they may include errors from PSPs that the user/pod isn't authorized to use. However, I think this is a worthwhile tradeoff. If this is a big concern, we could authorize all policies in the case where none admitted /validated the pod.

**Release note**:
```release-note
Improved PodSecurityPolicy admission latency, but validation errors are no longer limited to only errors from authorized policies.
```
This commit is contained in:
Kubernetes Submit Queue 2017-11-22 15:47:54 -08:00 committed by GitHub
commit 215844219b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 204 additions and 147 deletions

View File

@ -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",
],

View File

@ -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, ","))
}

View File

@ -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{