Merge pull request #42360 from liggitt/psp-namespaced-use-check

Automatic merge from submit-queue (batch tested with PRs 42360, 43109, 43737, 43853)

Include pod namespace in PSP 'use' authorization check

Follow up to https://github.com/kubernetes/kubernetes/pull/33080/files#diff-291b8dd7d08cc034975ddb3925dbb08fR341

Prior to this PR, when PodSecurityPolicy admission is active, you must be authorized to use a covering PodSecurityPolicy cluster-wide in order to create a pod. This PR changes that to only require a covering PodSecurityPolicy within the pod's namespace.

When used in concert with mechanisms that limits pods within a namespace to a particular set of nodes, this can be used to allow users to create privileged pods within specific namespaces only.

```release-note
Permission to use a PodSecurityPolicy can now be granted within a single namespace by allowing the `use` verb on the `podsecuritypolicies` resource within the namespace.
```
This commit is contained in:
Kubernetes Submit Queue 2017-03-31 00:34:22 -07:00 committed by GitHub
commit cc571d1833
2 changed files with 67 additions and 49 deletions

View File

@ -52,7 +52,7 @@ func init() {
} }
// PSPMatchFn allows plugging in how PSPs are matched against user information. // 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) ([]*extensions.PodSecurityPolicy, error) 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 {
@ -130,7 +130,7 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
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) matchedPolicies, err := c.pspMatcher(c.lister, a.GetUserInfo(), saInfo, c.authz, a.GetNamespace())
if err != nil { if err != nil {
return admission.NewForbidden(a, err) return admission.NewForbidden(a, err)
} }
@ -279,7 +279,7 @@ func (c *podSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions
// TODO: this will likely need optimization since the initial implementation will // TODO: this will likely need optimization since the initial implementation will
// always query for authorization. Needs scale testing and possibly checking against // always query for authorization. Needs scale testing and possibly checking against
// a cache. // a cache.
func getMatchingPolicies(lister extensionslisters.PodSecurityPolicyLister, user user.Info, sa user.Info, authz authorizer.Authorizer) ([]*extensions.PodSecurityPolicy, error) { func getMatchingPolicies(lister extensionslisters.PodSecurityPolicyLister, user user.Info, sa user.Info, authz authorizer.Authorizer, namespace string) ([]*extensions.PodSecurityPolicy, error) {
matchedPolicies := make([]*extensions.PodSecurityPolicy, 0) matchedPolicies := make([]*extensions.PodSecurityPolicy, 0)
list, err := lister.List(labels.Everything()) list, err := lister.List(labels.Everything())
@ -289,7 +289,7 @@ func getMatchingPolicies(lister extensionslisters.PodSecurityPolicyLister, user
for _, constraint := range list { for _, constraint := range list {
// if no user info exists then the API is being hit via the unsecured port. In this case authorize the request. // if no user info exists then the API is being hit via the unsecured port. In this case authorize the request.
if user == nil || authorizedForPolicy(user, constraint, authz) || authorizedForPolicy(sa, constraint, authz) { if user == nil || authorizedForPolicy(user, namespace, constraint, authz) || authorizedForPolicy(sa, namespace, constraint, authz) {
matchedPolicies = append(matchedPolicies, constraint) matchedPolicies = append(matchedPolicies, constraint)
} }
} }
@ -297,26 +297,26 @@ func getMatchingPolicies(lister extensionslisters.PodSecurityPolicyLister, user
return matchedPolicies, nil return matchedPolicies, nil
} }
// authorizedForPolicy returns true if info is authorized to perform a "get" on policy. // authorizedForPolicy returns true if info is authorized to perform the "use" verb on the policy resource.
func authorizedForPolicy(info user.Info, policy *extensions.PodSecurityPolicy, authz authorizer.Authorizer) bool { func authorizedForPolicy(info user.Info, namespace string, policy *extensions.PodSecurityPolicy, authz authorizer.Authorizer) bool {
if info == nil { if info == nil {
return false return false
} }
attr := buildAttributes(info, policy) attr := buildAttributes(info, namespace, policy)
allowed, reason, err := authz.Authorize(attr) allowed, reason, err := authz.Authorize(attr)
if err != nil { if err != nil {
glog.V(5).Infof("cannot authorized for policy: %v,%v", reason, err) glog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err)
} }
return allowed return allowed
} }
// 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, policy *extensions.PodSecurityPolicy) authorizer.Attributes { func buildAttributes(info user.Info, namespace string, policy *extensions.PodSecurityPolicy) authorizer.Attributes {
// TODO consider checking against the namespace that the pod is being // check against the namespace that the pod is being created in to allow per-namespace PSP grants.
// created in to allow per-namespace PSP definitions.
attr := authorizer.AttributesRecord{ attr := authorizer.AttributesRecord{
User: info, User: info,
Verb: "use", Verb: "use",
Namespace: namespace,
Name: policy.Name, Name: policy.Name,
APIGroup: extensions.GroupName, APIGroup: extensions.GroupName,
Resource: "podsecuritypolicies", Resource: "podsecuritypolicies",

View File

@ -57,18 +57,18 @@ func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmissi
// TestAlwaysAllowedAuthorizer is a testing struct for testing that fulfills the authorizer interface. // TestAlwaysAllowedAuthorizer is a testing struct for testing that fulfills the authorizer interface.
type TestAuthorizer struct { type TestAuthorizer struct {
// disallowed contains names of disallowed policies. Map is keyed by user.Info.GetName() // usernameToNamespaceToAllowedPSPs contains the map of allowed PSPs.
disallowed map[string][]string // if nil, all PSPs are allowed.
usernameToNamespaceToAllowedPSPs map[string]map[string]map[string]bool
} }
func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
disallowedForUser, _ := t.disallowed[a.GetUser().GetName()] if t.usernameToNamespaceToAllowedPSPs == nil {
for _, name := range disallowedForUser { return true, "", nil
if a.GetName() == name {
return false, "", nil
}
} }
return true, "", nil allowedInNamespace := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][a.GetNamespace()][a.GetName()]
allowedClusterWide := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][""][a.GetName()]
return (allowedInNamespace || allowedClusterWide), "", nil
} }
var _ authorizer.Authorizer = &TestAuthorizer{} var _ authorizer.Authorizer = &TestAuthorizer{}
@ -1546,17 +1546,21 @@ func TestGetMatchingPolicies(t *testing.T) {
} }
tests := map[string]struct { tests := map[string]struct {
user user.Info user user.Info
sa user.Info sa user.Info
expectedPolicies sets.String ns string
inPolicies []*extensions.PodSecurityPolicy expectedPolicies sets.String
disallowedPolicies map[string][]string inPolicies []*extensions.PodSecurityPolicy
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: &user.DefaultInfo{Name: "sa"},
disallowedPolicies: map[string][]string{ ns: "test",
"sa": {"policy"}, allowed: map[string]map[string]map[string]bool{
"user": {
"test": {"policy": true},
},
}, },
inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")},
expectedPolicies: sets.NewString("policy"), expectedPolicies: sets.NewString("policy"),
@ -1564,43 +1568,55 @@ func TestGetMatchingPolicies(t *testing.T) {
"policy allowed by sa": { "policy allowed by sa": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: &user.DefaultInfo{Name: "sa"}, sa: &user.DefaultInfo{Name: "sa"},
disallowedPolicies: map[string][]string{ ns: "test",
"user": {"policy"}, allowed: map[string]map[string]map[string]bool{
"sa": {
"test": {"policy": true},
},
}, },
inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")},
expectedPolicies: sets.NewString("policy"), expectedPolicies: sets.NewString("policy"),
}, },
"no policies allowed": { "no policies allowed": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: &user.DefaultInfo{Name: "sa"}, sa: &user.DefaultInfo{Name: "sa"},
disallowedPolicies: map[string][]string{ ns: "test",
"user": {"policy"}, allowed: map[string]map[string]map[string]bool{},
"sa": {"policy"},
},
inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")},
expectedPolicies: sets.NewString(), expectedPolicies: sets.NewString(),
}, },
"multiple policies allowed": { "multiple policies allowed": {
user: &user.DefaultInfo{Name: "user"}, user: &user.DefaultInfo{Name: "user"},
sa: &user.DefaultInfo{Name: "sa"}, sa: &user.DefaultInfo{Name: "sa"},
disallowedPolicies: map[string][]string{ ns: "test",
"user": {"policy1", "policy3"}, allowed: map[string]map[string]map[string]bool{
"sa": {"policy2", "policy3"}, "sa": {
"test": {"policy1": true},
"": {"policy4": true},
"other": {"policy6": true},
},
"user": {
"test": {"policy2": true},
"": {"policy5": true},
"other": {"policy7": true},
},
}, },
inPolicies: []*extensions.PodSecurityPolicy{ inPolicies: []*extensions.PodSecurityPolicy{
policyWithName("policy1"), // allowed by sa policyWithName("policy1"), // allowed by sa
policyWithName("policy2"), // allowed by user policyWithName("policy2"), // allowed by user
policyWithName("policy3"), // not allowed 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
}, },
expectedPolicies: sets.NewString("policy1", "policy2"), expectedPolicies: sets.NewString("policy1", "policy2", "policy4", "policy5"),
}, },
"policies are allowed for nil user info": { "policies are allowed for nil user info": {
user: nil, user: nil,
sa: &user.DefaultInfo{Name: "sa"}, sa: &user.DefaultInfo{Name: "sa"},
disallowedPolicies: map[string][]string{ ns: "test",
"user": {"policy1", "policy3"}, allowed: map[string]map[string]map[string]bool{}, // authorizer not consulted
"sa": {"policy2", "policy3"},
},
inPolicies: []*extensions.PodSecurityPolicy{ inPolicies: []*extensions.PodSecurityPolicy{
policyWithName("policy1"), policyWithName("policy1"),
policyWithName("policy2"), policyWithName("policy2"),
@ -1613,9 +1629,11 @@ func TestGetMatchingPolicies(t *testing.T) {
"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: nil,
disallowedPolicies: map[string][]string{ ns: "test",
"user": {"policy1", "policy3"}, allowed: map[string]map[string]map[string]bool{
"sa": {"policy2", "policy3"}, "user": {
"test": {"policy2": true},
},
}, },
inPolicies: []*extensions.PodSecurityPolicy{ inPolicies: []*extensions.PodSecurityPolicy{
policyWithName("policy1"), policyWithName("policy1"),
@ -1634,8 +1652,8 @@ func TestGetMatchingPolicies(t *testing.T) {
store.Add(psp) store.Add(psp)
} }
authz := &TestAuthorizer{disallowed: v.disallowedPolicies} authz := &TestAuthorizer{usernameToNamespaceToAllowedPSPs: v.allowed}
allowedPolicies, err := getMatchingPolicies(pspInformer.Lister(), v.user, v.sa, authz) allowedPolicies, err := getMatchingPolicies(pspInformer.Lister(), v.user, v.sa, authz, v.ns)
if err != nil { if err != nil {
t.Errorf("%s got unexpected error %#v", k, err) t.Errorf("%s got unexpected error %#v", k, err)
continue continue