Optimize PSP authorization

This commit is contained in:
Tim Allclair 2017-11-13 17:03:24 -08:00
parent 5337ff8009
commit 9673235583
No known key found for this signature in database
GPG Key ID: 434D16BCEF479EAB
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/core/helper:go_default_library",
"//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/extensions/internalversion:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/security/apparmor:go_default_library", "//pkg/security/apparmor:go_default_library",
"//pkg/security/podsecuritypolicy: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/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission: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/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
], ],

View File

@ -49,19 +49,15 @@ const (
// Register registers a plugin // Register registers a plugin
func Register(plugins *admission.Plugins) { func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
plugin := NewPlugin(psp.NewSimpleStrategyFactory(), getMatchingPolicies, true) plugin := newPlugin(psp.NewSimpleStrategyFactory(), true)
return plugin, nil 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. // PodSecurityPolicyPlugin holds state for and implements the admission plugin.
type PodSecurityPolicyPlugin struct { type PodSecurityPolicyPlugin struct {
*admission.Handler *admission.Handler
strategyFactory psp.StrategyFactory strategyFactory psp.StrategyFactory
pspMatcher PSPMatchFn
failOnNoPolicies bool failOnNoPolicies bool
authz authorizer.Authorizer authz authorizer.Authorizer
lister extensionslisters.PodSecurityPolicyLister lister extensionslisters.PodSecurityPolicyLister
@ -88,12 +84,11 @@ var _ admission.ValidationInterface = &PodSecurityPolicyPlugin{}
var _ genericadmissioninit.WantsAuthorizer = &PodSecurityPolicyPlugin{} var _ genericadmissioninit.WantsAuthorizer = &PodSecurityPolicyPlugin{}
var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &PodSecurityPolicyPlugin{} var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &PodSecurityPolicyPlugin{}
// NewPlugin creates a new PSP admission plugin. // newPlugin creates a new PSP admission plugin.
func NewPlugin(strategyFactory psp.StrategyFactory, pspMatcher PSPMatchFn, failOnNoPolicies bool) *PodSecurityPolicyPlugin { func newPlugin(strategyFactory psp.StrategyFactory, failOnNoPolicies bool) *PodSecurityPolicyPlugin {
return &PodSecurityPolicyPlugin{ return &PodSecurityPolicyPlugin{
Handler: admission.NewHandler(admission.Create, admission.Update), Handler: admission.NewHandler(admission.Create, admission.Update),
strategyFactory: strategyFactory, strategyFactory: strategyFactory,
pspMatcher: pspMatcher,
failOnNoPolicies: failOnNoPolicies, failOnNoPolicies: failOnNoPolicies,
} }
} }
@ -207,49 +202,60 @@ func (c *PodSecurityPolicyPlugin) computeSecurityContext(a admission.Attributes,
saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") 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 { if err != nil {
return nil, "", nil, err return nil, "", nil, err
} }
// if we have no policies and want to succeed then return. Otherwise we'll end up with no // 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. // 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 return pod, "", nil, nil
} }
// sort by name to make order deterministic // sort by name to make order deterministic
// TODO(liggitt): add priority field to allow admins to bucket differently // TODO(liggitt): add priority field to allow admins to bucket differently
sort.SliceStable(matchedPolicies, func(i, j int) bool { sort.SliceStable(policies, func(i, j int) bool {
return strings.Compare(matchedPolicies[i].Name, matchedPolicies[j].Name) < 0 return strings.Compare(policies[i].Name, policies[j].Name) < 0
}) })
providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) providers, errs := c.createProvidersFromPolicies(policies, pod.Namespace)
logProviders(a, pod, providers, errs) for _, err := range errs {
glog.V(4).Infof("provider creation error: %v", err)
}
if len(providers) == 0 { if len(providers) == 0 {
return nil, "", nil, fmt.Errorf("no providers available to validate pod request") 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 ( var (
allowedMutatedPod *api.Pod allowedMutatedPod *api.Pod
allowingMutatingPSP string allowingMutatingPSP string
// Map of PSP name to associated validation errors.
validationErrs = map[string]field.ErrorList{}
) )
for _, provider := range providers { for _, provider := range providers {
podCopy := pod.DeepCopy() podCopy := pod.DeepCopy()
if errs := assignSecurityContext(provider, podCopy, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 { 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 continue
} }
// the entire pod validated // 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 { switch {
case apiequality.Semantic.DeepEqual(pod, podCopy): case !mutated:
// if it validated without mutating anything, use this result // if it validated without mutating anything, use this result
return podCopy, provider.GetPSPName(), nil, nil return podCopy, provider.GetPSPName(), nil, nil
@ -261,11 +267,18 @@ func (c *PodSecurityPolicyPlugin) computeSecurityContext(a admission.Attributes,
} }
} }
if allowedMutatedPod == nil { if allowedMutatedPod != nil {
return nil, "", validationErrs, 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 // assignSecurityContext creates a security context for each container in the pod
@ -332,35 +345,18 @@ func (c *PodSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions
return providers, errs return providers, errs
} }
// getMatchingPolicies returns policies from the lister. For now this returns everything func isAuthorizedForPolicy(user, sa user.Info, namespace, policyName string, authz authorizer.Authorizer) bool {
// in the future it can filter based on UserInfo and permissions. // Check the service account first, as that is the more common use case.
// return authorizedForPolicy(sa, namespace, policyName, authz) ||
// TODO: this will likely need optimization since the initial implementation will authorizedForPolicy(user, namespace, policyName, authz)
// 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
} }
// authorizedForPolicy returns true if info is authorized to perform the "use" verb on the policy resource. // 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 { if info == nil {
return false return false
} }
attr := buildAttributes(info, namespace, policy) attr := buildAttributes(info, namespace, policyName)
decision, reason, err := authz.Authorize(attr) decision, reason, err := authz.Authorize(attr)
if err != nil { if err != nil {
glog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err) 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. // 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. // check against the namespace that the pod is being created in to allow per-namespace PSP grants.
attr := authorizer.AttributesRecord{ attr := authorizer.AttributesRecord{
User: info, User: info,
Verb: "use", Verb: "use",
Namespace: namespace, Namespace: namespace,
Name: policy.Name, Name: policyName,
APIGroup: extensions.GroupName, APIGroup: extensions.GroupName,
Resource: "podsecuritypolicies", Resource: "podsecuritypolicies",
ResourceRequest: true, ResourceRequest: true,
} }
return attr 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" apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
kadmission "k8s.io/apiserver/pkg/admission" kadmission "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
@ -37,7 +37,6 @@ import (
"k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/apparmor"
kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
@ -47,14 +46,21 @@ import (
const defaultContainerName = "test-c" const defaultContainerName = "test-c"
// NewTestAdmission provides an admission plugin with test implementations of internal structs. It uses // NewTestAdmission provides an admission plugin with test implementations of internal structs.
// an authorizer that always returns true. func NewTestAdmission(psps []*extensions.PodSecurityPolicy, authz authorizer.Authorizer) *PodSecurityPolicyPlugin {
func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) *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{ return &PodSecurityPolicyPlugin{
Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update),
strategyFactory: kpsp.NewSimpleStrategyFactory(), strategyFactory: kpsp.NewSimpleStrategyFactory(),
pspMatcher: getMatchingPolicies, authz: authz,
authz: &TestAuthorizer{},
lister: lister, lister: lister,
} }
} }
@ -434,7 +440,7 @@ func TestAdmitPreferNonmutating(t *testing.T) {
} }
for k, v := range tests { 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 { if v.shouldPassAdmit {
actualPodUser := (*int64)(nil) actualPodUser := (*int64)(nil)
@ -461,7 +467,7 @@ func TestAdmitPreferNonmutating(t *testing.T) {
} }
func TestFailClosedOnInvalidPod(t *testing.T) { func TestFailClosedOnInvalidPod(t *testing.T) {
plugin := NewTestAdmission(nil) plugin := NewTestAdmission(nil, nil)
pod := &v1.Pod{} 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{}) 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) { 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() originalPod := pod.DeepCopy()
plugin := NewTestAdmission(psps, authz)
plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister()) attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, "", kapi.Resource("pods").WithVersion("version"), "", op, userInfo)
attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", op, &user.DefaultInfo{})
err := plugin.Admit(attrs) err := plugin.Admit(attrs)
if shouldPassAdmit && err != nil { if shouldPassAdmit && err != nil {
@ -1987,24 +1985,24 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
} }
} }
func TestGetMatchingPolicies(t *testing.T) { func TestPolicyAuthorization(t *testing.T) {
policyWithName := func(name string) *extensions.PodSecurityPolicy { policyWithName := func(name string) *extensions.PodSecurityPolicy {
p := restrictivePSP() p := permissivePSP()
p.Name = name p.Name = name
return p return p
} }
tests := map[string]struct { tests := map[string]struct {
user user.Info user user.Info
sa user.Info sa string
ns string ns string
expectedPolicies sets.String expectedPolicy string
inPolicies []*extensions.PodSecurityPolicy inPolicies []*extensions.PodSecurityPolicy
allowed map[string]map[string]map[string]bool allowed map[string]map[string]map[string]bool
}{ }{
"policy allowed by user": { "policy allowed by user": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: &user.DefaultInfo{Name: "sa"}, sa: "sa",
ns: "test", ns: "test",
allowed: map[string]map[string]map[string]bool{ allowed: map[string]map[string]map[string]bool{
"user": { "user": {
@ -2012,34 +2010,34 @@ func TestGetMatchingPolicies(t *testing.T) {
}, },
}, },
inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")},
expectedPolicies: sets.NewString("policy"), expectedPolicy: "policy",
}, },
"policy allowed by sa": { "policy allowed by sa": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: &user.DefaultInfo{Name: "sa"}, sa: "sa",
ns: "test", ns: "test",
allowed: map[string]map[string]map[string]bool{ allowed: map[string]map[string]map[string]bool{
"sa": { serviceaccount.MakeUsername("test", "sa"): {
"test": {"policy": true}, "test": {"policy": true},
}, },
}, },
inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")},
expectedPolicies: sets.NewString("policy"), expectedPolicy: "policy",
}, },
"no policies allowed": { "no policies allowed": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: &user.DefaultInfo{Name: "sa"}, sa: "sa",
ns: "test", ns: "test",
allowed: map[string]map[string]map[string]bool{}, allowed: map[string]map[string]map[string]bool{},
inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")},
expectedPolicies: sets.NewString(), expectedPolicy: "",
}, },
"multiple policies allowed": { "multiple policies allowed": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: &user.DefaultInfo{Name: "sa"}, sa: "sa",
ns: "test", ns: "test",
allowed: map[string]map[string]map[string]bool{ allowed: map[string]map[string]map[string]bool{
"sa": { serviceaccount.MakeUsername("test", "sa"): {
"test": {"policy1": true}, "test": {"policy1": true},
"": {"policy4": true}, "": {"policy4": true},
"other": {"policy6": true}, "other": {"policy6": true},
@ -2051,22 +2049,23 @@ func TestGetMatchingPolicies(t *testing.T) {
}, },
}, },
inPolicies: []*extensions.PodSecurityPolicy{ inPolicies: []*extensions.PodSecurityPolicy{
policyWithName("policy1"), // allowed by sa // Prefix to force checking these policies first.
policyWithName("policy2"), // allowed by user policyWithName("a_policy1"), // not allowed in this namespace
policyWithName("policy3"), // not allowed policyWithName("a_policy2"), // not allowed in this namespace
policyWithName("policy4"), // allowed by sa at cluster level policyWithName("policy2"), // allowed by sa
policyWithName("policy5"), // allowed by user at cluster level policyWithName("policy3"), // allowed by user
policyWithName("policy6"), // not allowed in this namespace policyWithName("policy4"), // not allowed
policyWithName("policy7"), // not allowed in this namespace 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": { "policies are not allowed for nil user info": {
user: nil, user: nil,
sa: &user.DefaultInfo{Name: "sa"}, sa: "sa",
ns: "test", ns: "test",
allowed: map[string]map[string]map[string]bool{ allowed: map[string]map[string]map[string]bool{
"sa": { serviceaccount.MakeUsername("test", "sa"): {
"test": {"policy1": true}, "test": {"policy1": true},
}, },
"user": { "user": {
@ -2079,14 +2078,14 @@ func TestGetMatchingPolicies(t *testing.T) {
policyWithName("policy3"), policyWithName("policy3"),
}, },
// only the policies for the sa are allowed when user info is nil // 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": { "policies are not allowed for nil sa info": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: nil, sa: "",
ns: "test", ns: "test",
allowed: map[string]map[string]map[string]bool{ allowed: map[string]map[string]map[string]bool{
"sa": { serviceaccount.MakeUsername("test", "sa"): {
"test": {"policy1": true}, "test": {"policy1": true},
}, },
"user": { "user": {
@ -2099,14 +2098,14 @@ func TestGetMatchingPolicies(t *testing.T) {
policyWithName("policy3"), policyWithName("policy3"),
}, },
// only the policies for the user are allowed when sa info is nil // 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": { "policies are not allowed for nil sa and user info": {
user: nil, user: nil,
sa: nil, sa: "",
ns: "test", ns: "test",
allowed: map[string]map[string]map[string]bool{ allowed: map[string]map[string]map[string]bool{
"sa": { serviceaccount.MakeUsername("test", "sa"): {
"test": {"policy1": true}, "test": {"policy1": true},
}, },
"user": { "user": {
@ -2119,30 +2118,110 @@ func TestGetMatchingPolicies(t *testing.T) {
policyWithName("policy3"), policyWithName("policy3"),
}, },
// no policies are allowed if sa and user are both nil // no policies are allowed if sa and user are both nil
expectedPolicies: sets.NewString(), expectedPolicy: "",
}, },
} }
for k, v := range tests { for k, v := range tests {
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) var (
pspInformer := informerFactory.Extensions().InternalVersion().PodSecurityPolicies() oldPod *kapi.Pod
store := pspInformer.Informer().GetStore() shouldPass = v.expectedPolicy != ""
for _, psp := range v.inPolicies { authz = &TestAuthorizer{usernameToNamespaceToAllowedPSPs: v.allowed}
store.Add(psp) 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} func TestPolicyAuthorizationErrors(t *testing.T) {
allowedPolicies, err := getMatchingPolicies(pspInformer.Lister(), v.user, v.sa, authz, v.ns) policyWithName := func(name string) *extensions.PodSecurityPolicy {
if err != nil { p := restrictivePSP()
t.Errorf("%s got unexpected error %#v", k, err) p.Name = name
continue return p
} }
allowedPolicyNames := sets.NewString()
for _, p := range allowedPolicies { const (
allowedPolicyNames.Insert(p.Name) sa = "sa"
} ns = "test"
if !v.expectedPolicies.Equal(allowedPolicyNames) { userName = "user"
t.Errorf("%s received unexpected policies. Expected %#v but got %#v", k, v.expectedPolicies.List(), allowedPolicyNames.List()) )
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 { func goodPod() *kapi.Pod {
return &kapi.Pod{ return &kapi.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Namespace: "namespace",
Annotations: map[string]string{}, Annotations: map[string]string{},
}, },
Spec: kapi.PodSpec{ Spec: kapi.PodSpec{