mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
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:
commit
cc571d1833
@ -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",
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user