Merge pull request #35300 from deads2k/rbac-17-subjectlocator

Automatic merge from submit-queue (batch tested with PRs 35300, 36709, 37643, 37813, 37697)

add rbac action to subjects type

This adds the ability to go from an authorization action to the list subjects who have the power to perform the action.  This will be used to either back an RBAC specific endpoint or generic authorization endpoint.  Because of the way authorization works today, the set of subjects returned will always be a subset of those with access since any authorizer can say yes.

@kubernetes/sig-auth
This commit is contained in:
Kubernetes Submit Queue 2016-12-03 08:55:54 -08:00 committed by GitHub
commit b1a3f3794a
6 changed files with 295 additions and 19 deletions

View File

@ -190,28 +190,29 @@ func appliesToUser(user user.Info, subject rbac.Subject, namespace string) bool
} }
// NewTestRuleResolver returns a rule resolver from lists of role objects. // NewTestRuleResolver returns a rule resolver from lists of role objects.
func NewTestRuleResolver(roles []*rbac.Role, roleBindings []*rbac.RoleBinding, clusterRoles []*rbac.ClusterRole, clusterRoleBindings []*rbac.ClusterRoleBinding) AuthorizationRuleResolver { func NewTestRuleResolver(roles []*rbac.Role, roleBindings []*rbac.RoleBinding, clusterRoles []*rbac.ClusterRole, clusterRoleBindings []*rbac.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) {
r := staticRoles{ r := StaticRoles{
roles: roles, roles: roles,
roleBindings: roleBindings, roleBindings: roleBindings,
clusterRoles: clusterRoles, clusterRoles: clusterRoles,
clusterRoleBindings: clusterRoleBindings, clusterRoleBindings: clusterRoleBindings,
} }
return newMockRuleResolver(&r) return newMockRuleResolver(&r), &r
} }
func newMockRuleResolver(r *staticRoles) AuthorizationRuleResolver { func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver {
return NewDefaultRuleResolver(r, r, r, r) return NewDefaultRuleResolver(r, r, r, r)
} }
type staticRoles struct { // StaticRoles is a rule resolver that resolves from lists of role objects.
type StaticRoles struct {
roles []*rbac.Role roles []*rbac.Role
roleBindings []*rbac.RoleBinding roleBindings []*rbac.RoleBinding
clusterRoles []*rbac.ClusterRole clusterRoles []*rbac.ClusterRole
clusterRoleBindings []*rbac.ClusterRoleBinding clusterRoleBindings []*rbac.ClusterRoleBinding
} }
func (r *staticRoles) GetRole(namespace, name string) (*rbac.Role, error) { func (r *StaticRoles) GetRole(namespace, name string) (*rbac.Role, error) {
if len(namespace) == 0 { if len(namespace) == 0 {
return nil, errors.New("must provide namespace when getting role") return nil, errors.New("must provide namespace when getting role")
} }
@ -223,7 +224,7 @@ func (r *staticRoles) GetRole(namespace, name string) (*rbac.Role, error) {
return nil, errors.New("role not found") return nil, errors.New("role not found")
} }
func (r *staticRoles) GetClusterRole(name string) (*rbac.ClusterRole, error) { func (r *StaticRoles) GetClusterRole(name string) (*rbac.ClusterRole, error) {
for _, clusterRole := range r.clusterRoles { for _, clusterRole := range r.clusterRoles {
if clusterRole.Name == name { if clusterRole.Name == name {
return clusterRole, nil return clusterRole, nil
@ -232,7 +233,7 @@ func (r *staticRoles) GetClusterRole(name string) (*rbac.ClusterRole, error) {
return nil, errors.New("role not found") return nil, errors.New("role not found")
} }
func (r *staticRoles) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error) { func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error) {
if len(namespace) == 0 { if len(namespace) == 0 {
return nil, errors.New("must provide namespace when listing role bindings") return nil, errors.New("must provide namespace when listing role bindings")
} }
@ -248,6 +249,6 @@ func (r *staticRoles) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, e
return roleBindingList, nil return roleBindingList, nil
} }
func (r *staticRoles) ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error) { func (r *StaticRoles) ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error) {
return r.clusterRoleBindings, nil return r.clusterRoleBindings, nil
} }

View File

@ -72,7 +72,7 @@ func TestDefaultRuleResolver(t *testing.T) {
Resources: []string{"*"}, Resources: []string{"*"},
} }
staticRoles1 := staticRoles{ staticRoles1 := StaticRoles{
roles: []*rbac.Role{ roles: []*rbac.Role{
{ {
ObjectMeta: api.ObjectMeta{Namespace: "namespace1", Name: "readthings"}, ObjectMeta: api.ObjectMeta{Namespace: "namespace1", Name: "readthings"},
@ -111,7 +111,7 @@ func TestDefaultRuleResolver(t *testing.T) {
} }
tests := []struct { tests := []struct {
staticRoles StaticRoles
// For a given context, what are the rules that apply? // For a given context, what are the rules that apply?
user user.Info user user.Info
@ -119,32 +119,32 @@ func TestDefaultRuleResolver(t *testing.T) {
effectiveRules []rbac.PolicyRule effectiveRules []rbac.PolicyRule
}{ }{
{ {
staticRoles: staticRoles1, StaticRoles: staticRoles1,
user: &user.DefaultInfo{Name: "foobar"}, user: &user.DefaultInfo{Name: "foobar"},
namespace: "namespace1", namespace: "namespace1",
effectiveRules: []rbac.PolicyRule{ruleReadPods, ruleReadServices}, effectiveRules: []rbac.PolicyRule{ruleReadPods, ruleReadServices},
}, },
{ {
staticRoles: staticRoles1, StaticRoles: staticRoles1,
user: &user.DefaultInfo{Name: "foobar"}, user: &user.DefaultInfo{Name: "foobar"},
namespace: "namespace2", namespace: "namespace2",
effectiveRules: []rbac.PolicyRule{}, effectiveRules: []rbac.PolicyRule{},
}, },
{ {
staticRoles: staticRoles1, StaticRoles: staticRoles1,
// Same as above but without a namespace. Only cluster rules should apply. // Same as above but without a namespace. Only cluster rules should apply.
user: &user.DefaultInfo{Name: "foobar", Groups: []string{"admin"}}, user: &user.DefaultInfo{Name: "foobar", Groups: []string{"admin"}},
effectiveRules: []rbac.PolicyRule{ruleAdmin}, effectiveRules: []rbac.PolicyRule{ruleAdmin},
}, },
{ {
staticRoles: staticRoles1, StaticRoles: staticRoles1,
user: &user.DefaultInfo{}, user: &user.DefaultInfo{},
effectiveRules: []rbac.PolicyRule{}, effectiveRules: []rbac.PolicyRule{},
}, },
} }
for i, tc := range tests { for i, tc := range tests {
ruleResolver := newMockRuleResolver(&tc.staticRoles) ruleResolver := newMockRuleResolver(&tc.StaticRoles)
rules, err := ruleResolver.RulesFor(tc.user, tc.namespace) rules, err := ruleResolver.RulesFor(tc.user, tc.namespace)
if err != nil { if err != nil {
t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err) t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err)

View File

@ -12,19 +12,26 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = ["rbac.go"], srcs = [
"rbac.go",
"subject_locator.go",
],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library", "//pkg/apis/rbac/validation:go_default_library",
"//pkg/auth/authorizer:go_default_library", "//pkg/auth/authorizer:go_default_library",
"//pkg/auth/user:go_default_library", "//pkg/auth/user:go_default_library",
"//pkg/util/errors:go_default_library",
], ],
) )
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["rbac_test.go"], srcs = [
"rbac_test.go",
"subject_locator_test.go",
],
library = "go_default_library", library = "go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [

View File

@ -221,7 +221,7 @@ func TestAuthorizer(t *testing.T) {
}, },
} }
for i, tt := range tests { for i, tt := range tests {
ruleResolver := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings) ruleResolver, _ := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := RBACAuthorizer{tt.superUser, ruleResolver} a := RBACAuthorizer{tt.superUser, ruleResolver}
for _, attr := range tt.shouldPass { for _, attr := range tt.shouldPass {
if authorized, _, _ := a.Authorize(attr); !authorized { if authorized, _, _ := a.Authorize(attr); !authorized {

View File

@ -0,0 +1,117 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package rbac implements the authorizer.Authorizer interface using roles base access control.
package rbac
import (
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
)
type RoleToRuleMapper interface {
// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace
// of the role binding, the empty string if a cluster role binding.
GetRoleReferenceRules(roleRef rbac.RoleRef, namespace string) ([]rbac.PolicyRule, error)
}
type SubjectAccessEvaluator struct {
superUser string
roleBindingLister validation.RoleBindingLister
clusterRoleBindingLister validation.ClusterRoleBindingLister
roleToRuleMapper RoleToRuleMapper
}
func NewSubjectAccessEvaluator(roles validation.RoleGetter, roleBindings validation.RoleBindingLister, clusterRoles validation.ClusterRoleGetter, clusterRoleBindings validation.ClusterRoleBindingLister, superUser string) *SubjectAccessEvaluator {
subjectLocator := &SubjectAccessEvaluator{
superUser: superUser,
roleBindingLister: roleBindings,
clusterRoleBindingLister: clusterRoleBindings,
roleToRuleMapper: validation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}
return subjectLocator
}
// AllowedSubjects returns the subjects that can perform an action and any errors encountered while computing the list.
// It is possible to have both subjects and errors returned if some rolebindings couldn't be resolved, but others could be.
func (r *SubjectAccessEvaluator) AllowedSubjects(requestAttributes authorizer.Attributes) ([]rbac.Subject, error) {
subjects := []rbac.Subject{{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup}}
if len(r.superUser) > 0 {
subjects = append(subjects, rbac.Subject{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: r.superUser})
}
errorlist := []error{}
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
errorlist = append(errorlist, err)
} else {
for _, clusterRoleBinding := range clusterRoleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive,
// missing a reference is bad, but we can continue with other rolebindings and still have a list
// that does not contain any invalid values
errorlist = append(errorlist, err)
}
if RulesAllow(requestAttributes, rules...) {
subjects = append(subjects, clusterRoleBinding.Subjects...)
}
}
}
if namespace := requestAttributes.GetNamespace(); len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
errorlist = append(errorlist, err)
} else {
for _, roleBinding := range roleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive,
// missing a reference is bad, but we can continue with other rolebindings and still have a list
// that does not contain any invalid values
errorlist = append(errorlist, err)
}
if RulesAllow(requestAttributes, rules...) {
subjects = append(subjects, roleBinding.Subjects...)
}
}
}
}
dedupedSubjects := []rbac.Subject{}
for _, subject := range subjects {
found := false
for _, curr := range dedupedSubjects {
if curr == subject {
found = true
break
}
}
if !found {
dedupedSubjects = append(dedupedSubjects, subject)
}
}
return subjects, utilerrors.NewAggregate(errorlist)
}

View File

@ -0,0 +1,151 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rbac
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
)
func TestSubjectLocator(t *testing.T) {
type actionToSubjects struct {
action authorizer.Attributes
subjects []rbac.Subject
}
tests := []struct {
name string
roles []*rbac.Role
roleBindings []*rbac.RoleBinding
clusterRoles []*rbac.ClusterRole
clusterRoleBindings []*rbac.ClusterRoleBinding
superUser string
actionsToSubjects []actionToSubjects
}{
{
name: "no super user, star matches star",
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("admin", "User:super-admin", "Group:super-admins"),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
actionsToSubjects: []actionToSubjects{
{
&defaultAttributes{"", "", "get", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
{Kind: rbac.UserKind, Name: "admin"},
{Kind: rbac.GroupKind, Name: "admins"},
},
},
{
// cluster role matches star in namespace
&defaultAttributes{"", "", "*", "Pods", "", "*", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
{
// empty ns
&defaultAttributes{"", "", "*", "Pods", "", "", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
},
},
{
name: "super user, local roles work",
superUser: "foo",
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("admin", "User:super-admin", "Group:super-admins"),
},
roles: []*rbac.Role{
newRole("admin", "ns1", newRule("get", "*", "Pods", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToRole, "User:admin", "Group:admins"),
},
actionsToSubjects: []actionToSubjects{
{
&defaultAttributes{"", "", "get", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: "foo"},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
{Kind: rbac.UserKind, Name: "admin"},
{Kind: rbac.GroupKind, Name: "admins"},
},
},
{
// verb matchies correctly
&defaultAttributes{"", "", "create", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: "foo"},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
{
// binding only works in correct ns
&defaultAttributes{"", "", "get", "Pods", "", "ns2", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: "foo"},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
},
},
}
for _, tt := range tests {
ruleResolver, lister := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := SubjectAccessEvaluator{tt.superUser, lister, lister, ruleResolver}
for i, action := range tt.actionsToSubjects {
actualSubjects, err := a.AllowedSubjects(action.action)
if err != nil {
t.Errorf("case %q %d: error %v", tt.name, i, err)
}
if !reflect.DeepEqual(actualSubjects, action.subjects) {
t.Errorf("case %q %d: expected %v actual %v", tt.name, i, action.subjects, actualSubjects)
}
}
}
}