wire in ctx to rbac plugins

This commit is contained in:
Mangirdas Judeikis 2024-09-13 12:03:47 +03:00
parent 8d402c9941
commit 4e4eb8c5c9
22 changed files with 297 additions and 71 deletions

View File

@ -239,7 +239,7 @@ func (pl PolicyList) Authorize(ctx context.Context, a authorizer.Attributes) (au
} }
// RulesFor returns rules for the given user and namespace. // RulesFor returns rules for the given user and namespace.
func (pl PolicyList) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (pl PolicyList) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var ( var (
resourceRules []authorizer.ResourceRuleInfo resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo nonResourceRules []authorizer.NonResourceRuleInfo

View File

@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/apis/abac" "k8s.io/kubernetes/pkg/apis/abac"
"k8s.io/kubernetes/pkg/apis/abac/v0" "k8s.io/kubernetes/pkg/apis/abac/v0"
"k8s.io/kubernetes/pkg/apis/abac/v1beta1" "k8s.io/kubernetes/pkg/apis/abac/v1beta1"
@ -324,7 +325,7 @@ func TestRulesFor(t *testing.T) {
User: &tc.User, User: &tc.User,
Namespace: tc.Namespace, Namespace: tc.Namespace,
} }
resourceRules, nonResourceRules, _, _ := a.RulesFor(attr.GetUser(), attr.GetNamespace()) resourceRules, nonResourceRules, _, _ := a.RulesFor(genericapirequest.NewContext(), attr.GetUser(), attr.GetNamespace())
actualResourceRules := getResourceRules(resourceRules) actualResourceRules := getResourceRules(resourceRules)
if !reflect.DeepEqual(tc.ExpectResourceRules, actualResourceRules) { if !reflect.DeepEqual(tc.ExpectResourceRules, actualResourceRules) {
t.Logf("tc: %v -> attr %v", tc, attr) t.Logf("tc: %v -> attr %v", tc, attr)

View File

@ -78,8 +78,8 @@ func (r *reloadableAuthorizerResolver) Authorize(ctx context.Context, a authoriz
return r.current.Load().authorizer.Authorize(ctx, a) return r.current.Load().authorizer.Authorize(ctx, a)
} }
func (r *reloadableAuthorizerResolver) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (r *reloadableAuthorizerResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
return r.current.Load().ruleResolver.RulesFor(user, namespace) return r.current.Load().ruleResolver.RulesFor(ctx, user, namespace)
} }
// newForConfig constructs // newForConfig constructs

View File

@ -78,7 +78,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation
} }
} }
resourceInfo, nonResourceInfo, incomplete, err := r.ruleResolver.RulesFor(user, namespace) resourceInfo, nonResourceInfo, incomplete, err := r.ruleResolver.RulesFor(ctx, user, namespace)
ret := &authorizationapi.SelfSubjectRulesReview{ ret := &authorizationapi.SelfSubjectRulesReview{
Status: authorizationapi.SubjectRulesReviewStatus{ Status: authorizationapi.SubjectRulesReviewStatus{

View File

@ -62,6 +62,6 @@ type AuthorizerAdapter struct {
} }
// GetClusterRole returns the corresponding ClusterRole by name // GetClusterRole returns the corresponding ClusterRole by name
func (a AuthorizerAdapter) GetClusterRole(name string) (*rbacv1.ClusterRole, error) { func (a AuthorizerAdapter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) {
return a.Registry.GetClusterRole(genericapirequest.NewContext(), name, &metav1.GetOptions{}) return a.Registry.GetClusterRole(genericapirequest.WithNamespace(ctx, ""), name, &metav1.GetOptions{})
} }

View File

@ -81,7 +81,7 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati
if err != nil { if err != nil {
return nil, err return nil, err
} }
rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, metav1.NamespaceNone) rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, metav1.NamespaceNone)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -115,7 +115,7 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec
if err != nil { if err != nil {
return nil, err return nil, err
} }
rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, metav1.NamespaceNone) rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, metav1.NamespaceNone)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -61,8 +61,8 @@ type AuthorizerAdapter struct {
Registry Registry Registry Registry
} }
func (a AuthorizerAdapter) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) { func (a AuthorizerAdapter) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) {
list, err := a.Registry.ListClusterRoleBindings(genericapirequest.NewContext(), &metainternalversion.ListOptions{}) list, err := a.Registry.ListClusterRoleBindings(genericapirequest.WithNamespace(ctx, ""), &metainternalversion.ListOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -62,6 +62,6 @@ type AuthorizerAdapter struct {
} }
// GetRole returns the corresponding Role by name in specified namespace // GetRole returns the corresponding Role by name in specified namespace
func (a AuthorizerAdapter) GetRole(namespace, name string) (*rbacv1.Role, error) { func (a AuthorizerAdapter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) {
return a.Registry.GetRole(genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace), name, &metav1.GetOptions{}) return a.Registry.GetRole(genericapirequest.WithNamespace(ctx, namespace), name, &metav1.GetOptions{})
} }

View File

@ -89,7 +89,7 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati
if err != nil { if err != nil {
return nil, err return nil, err
} }
rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, namespace) rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -130,7 +130,7 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec
if err != nil { if err != nil {
return nil, err return nil, err
} }
rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, namespace) rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -61,8 +61,8 @@ type AuthorizerAdapter struct {
Registry Registry Registry Registry
} }
func (a AuthorizerAdapter) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) { func (a AuthorizerAdapter) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) {
list, err := a.Registry.ListRoleBindings(genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace), &metainternalversion.ListOptions{}) list, err := a.Registry.ListRoleBindings(genericapirequest.WithNamespace(ctx, namespace), &metainternalversion.ListOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -37,16 +37,16 @@ import (
type AuthorizationRuleResolver interface { type AuthorizationRuleResolver interface {
// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace // 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. // of the role binding, the empty string if a cluster role binding.
GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error)
// RulesFor returns the list of rules that apply to a given user in a given namespace and error. If an error is returned, the slice of // RulesFor returns the list of rules that apply to a given user in a given namespace and error. If an error is returned, the slice of
// PolicyRules may not be complete, but it contains all retrievable rules. This is done because policy rules are purely additive and policy determinations // PolicyRules may not be complete, but it contains all retrievable rules. This is done because policy rules are purely additive and policy determinations
// can be made on the basis of those rules that are found. // can be made on the basis of those rules that are found.
RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error) RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error)
// VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, and each error encountered resolving those rules. // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, and each error encountered resolving those rules.
// If visitor() returns false, visiting is short-circuited. // If visitor() returns false, visiting is short-circuited.
VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool)
} }
// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role. // ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
@ -59,7 +59,7 @@ func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleReso
} }
namespace, _ := genericapirequest.NamespaceFrom(ctx) namespace, _ := genericapirequest.NamespaceFrom(ctx)
ownerRules, err := ruleResolver.RulesFor(user, namespace) ownerRules, err := ruleResolver.RulesFor(ctx, user, namespace)
if err != nil { if err != nil {
// As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue. // As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
klog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err) klog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
@ -100,24 +100,24 @@ func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBinding
} }
type RoleGetter interface { type RoleGetter interface {
GetRole(namespace, name string) (*rbacv1.Role, error) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error)
} }
type RoleBindingLister interface { type RoleBindingLister interface {
ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error)
} }
type ClusterRoleGetter interface { type ClusterRoleGetter interface {
GetClusterRole(name string) (*rbacv1.ClusterRole, error) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error)
} }
type ClusterRoleBindingLister interface { type ClusterRoleBindingLister interface {
ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error)
} }
func (r *DefaultRuleResolver) RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error) { func (r *DefaultRuleResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error) {
visitor := &ruleAccumulator{} visitor := &ruleAccumulator{}
r.VisitRulesFor(user, namespace, visitor.visit) r.VisitRulesFor(ctx, user, namespace, visitor.visit)
return visitor.rules, utilerrors.NewAggregate(visitor.errors) return visitor.rules, utilerrors.NewAggregate(visitor.errors)
} }
@ -176,8 +176,8 @@ func (d *roleBindingDescriber) String() string {
) )
} }
func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) { func (r *DefaultRuleResolver) VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil { if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(ctx); err != nil {
if !visitor(nil, nil, err) { if !visitor(nil, nil, err) {
return return
} }
@ -188,7 +188,7 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi
if !applies { if !applies {
continue continue
} }
rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") rules, err := r.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, "")
if err != nil { if err != nil {
if !visitor(nil, nil, err) { if !visitor(nil, nil, err) {
return return
@ -206,7 +206,7 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi
} }
if len(namespace) > 0 { if len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil { if roleBindings, err := r.roleBindingLister.ListRoleBindings(ctx, namespace); err != nil {
if !visitor(nil, nil, err) { if !visitor(nil, nil, err) {
return return
} }
@ -217,7 +217,7 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi
if !applies { if !applies {
continue continue
} }
rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace) rules, err := r.GetRoleReferenceRules(ctx, roleBinding.RoleRef, namespace)
if err != nil { if err != nil {
if !visitor(nil, nil, err) { if !visitor(nil, nil, err) {
return return
@ -237,17 +237,17 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi
} }
// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding. // GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
func (r *DefaultRuleResolver) GetRoleReferenceRules(roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) { func (r *DefaultRuleResolver) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) {
switch roleRef.Kind { switch roleRef.Kind {
case "Role": case "Role":
role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name) role, err := r.roleGetter.GetRole(ctx, bindingNamespace, roleRef.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return role.Rules, nil return role.Rules, nil
case "ClusterRole": case "ClusterRole":
clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name) clusterRole, err := r.clusterRoleGetter.GetClusterRole(ctx, roleRef.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -326,7 +326,7 @@ type StaticRoles struct {
clusterRoleBindings []*rbacv1.ClusterRoleBinding clusterRoleBindings []*rbacv1.ClusterRoleBinding
} }
func (r *StaticRoles) GetRole(namespace, name string) (*rbacv1.Role, error) { func (r *StaticRoles) GetRole(ctx context.Context, namespace, name string) (*rbacv1.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")
} }
@ -338,7 +338,7 @@ func (r *StaticRoles) GetRole(namespace, name string) (*rbacv1.Role, error) {
return nil, errors.New("role not found") return nil, errors.New("role not found")
} }
func (r *StaticRoles) GetClusterRole(name string) (*rbacv1.ClusterRole, error) { func (r *StaticRoles) GetClusterRole(ctx context.Context, name string) (*rbacv1.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
@ -347,7 +347,7 @@ func (r *StaticRoles) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
return nil, errors.New("clusterrole not found") return nil, errors.New("clusterrole not found")
} }
func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) { func (r *StaticRoles) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.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")
} }
@ -363,6 +363,6 @@ func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding,
return roleBindingList, nil return roleBindingList, nil
} }
func (r *StaticRoles) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) { func (r *StaticRoles) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) {
return r.clusterRoleBindings, nil return r.clusterRoleBindings, nil
} }

View File

@ -27,6 +27,7 @@ import (
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
) )
// compute a hash of a policy rule so we can sort in a deterministic order // compute a hash of a policy rule so we can sort in a deterministic order
@ -145,7 +146,7 @@ func TestDefaultRuleResolver(t *testing.T) {
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(genericapirequest.NewContext(), 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)
continue continue

View File

@ -95,7 +95,7 @@ var (
csiNodeResource = storageapi.Resource("csinodes") csiNodeResource = storageapi.Resource("csinodes")
) )
func (r *NodeAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (r *NodeAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
if _, isNode := r.identifier.NodeIdentity(user); isNode { if _, isNode := r.identifier.NodeIdentity(user); isNode {
// indicate nodes do not have fully enumerated permissions // indicate nodes do not have fully enumerated permissions
return nil, nil, true, fmt.Errorf("node authorizer does not support user rule resolution") return nil, nil, true, fmt.Errorf("node authorizer does not support user rule resolution")

View File

@ -39,12 +39,12 @@ type RequestToRuleMapper interface {
// Any rule returned is still valid, since rules are deny by default. If you can pass with the rules // Any rule returned is still valid, since rules are deny by default. If you can pass with the rules
// supplied, you do not have to fail the request. If you cannot, you should indicate the error along // supplied, you do not have to fail the request. If you cannot, you should indicate the error along
// with your denial. // with your denial.
RulesFor(subject user.Info, namespace string) ([]rbacv1.PolicyRule, error) RulesFor(ctx context.Context, subject user.Info, namespace string) ([]rbacv1.PolicyRule, error)
// VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace,
// and each error encountered resolving those rules. Rule may be nil if err is non-nil. // and each error encountered resolving those rules. Rule may be nil if err is non-nil.
// If visitor() returns false, visiting is short-circuited. // If visitor() returns false, visiting is short-circuited.
VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool)
} }
type RBACAuthorizer struct { type RBACAuthorizer struct {
@ -75,7 +75,7 @@ func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule,
func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) { func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes} ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}
r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit) r.authorizationRuleResolver.VisitRulesFor(ctx, requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
if ruleCheckingVisitor.allowed { if ruleCheckingVisitor.allowed {
return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
} }
@ -126,13 +126,13 @@ func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes author
return authorizer.DecisionNoOpinion, reason, nil return authorizer.DecisionNoOpinion, reason, nil
} }
func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (r *RBACAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var ( var (
resourceRules []authorizer.ResourceRuleInfo resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo nonResourceRules []authorizer.NonResourceRuleInfo
) )
policyRules, err := r.authorizationRuleResolver.RulesFor(user, namespace) policyRules, err := r.authorizationRuleResolver.RulesFor(ctx, user, namespace)
for _, policyRule := range policyRules { for _, policyRule := range policyRules {
if len(policyRule.Resources) > 0 { if len(policyRule.Resources) > 0 {
r := authorizer.DefaultResourceRuleInfo{ r := authorizer.DefaultResourceRuleInfo{
@ -196,7 +196,7 @@ type RoleGetter struct {
Lister rbaclisters.RoleLister Lister rbaclisters.RoleLister
} }
func (g *RoleGetter) GetRole(namespace, name string) (*rbacv1.Role, error) { func (g *RoleGetter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) {
return g.Lister.Roles(namespace).Get(name) return g.Lister.Roles(namespace).Get(name)
} }
@ -204,7 +204,7 @@ type RoleBindingLister struct {
Lister rbaclisters.RoleBindingLister Lister rbaclisters.RoleBindingLister
} }
func (l *RoleBindingLister) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) { func (l *RoleBindingLister) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) {
return l.Lister.RoleBindings(namespace).List(labels.Everything()) return l.Lister.RoleBindings(namespace).List(labels.Everything())
} }
@ -212,7 +212,7 @@ type ClusterRoleGetter struct {
Lister rbaclisters.ClusterRoleLister Lister rbaclisters.ClusterRoleLister
} }
func (g *ClusterRoleGetter) GetClusterRole(name string) (*rbacv1.ClusterRole, error) { func (g *ClusterRoleGetter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) {
return g.Lister.Get(name) return g.Lister.Get(name)
} }
@ -220,6 +220,6 @@ type ClusterRoleBindingLister struct {
Lister rbaclisters.ClusterRoleBindingLister Lister rbaclisters.ClusterRoleBindingLister
} }
func (l *ClusterRoleBindingLister) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) { func (l *ClusterRoleBindingLister) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) {
return l.Lister.List(labels.Everything()) return l.Lister.List(labels.Everything())
} }

View File

@ -18,6 +18,8 @@ limitations under the License.
package rbac package rbac
import ( import (
"context"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
@ -28,11 +30,11 @@ import (
type RoleToRuleMapper interface { type RoleToRuleMapper interface {
// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace // 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. // of the role binding, the empty string if a cluster role binding.
GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error)
} }
type SubjectLocator interface { type SubjectLocator interface {
AllowedSubjects(attributes authorizer.Attributes) ([]rbacv1.Subject, error) AllowedSubjects(ctx context.Context, attributes authorizer.Attributes) ([]rbacv1.Subject, error)
} }
var _ = SubjectLocator(&SubjectAccessEvaluator{}) var _ = SubjectLocator(&SubjectAccessEvaluator{})
@ -59,19 +61,19 @@ func NewSubjectAccessEvaluator(roles rbacregistryvalidation.RoleGetter, roleBind
// AllowedSubjects returns the subjects that can perform an action and any errors encountered while computing the list. // 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. // 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) ([]rbacv1.Subject, error) { func (r *SubjectAccessEvaluator) AllowedSubjects(ctx context.Context, requestAttributes authorizer.Attributes) ([]rbacv1.Subject, error) {
subjects := []rbacv1.Subject{{Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: user.SystemPrivilegedGroup}} subjects := []rbacv1.Subject{{Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: user.SystemPrivilegedGroup}}
if len(r.superUser) > 0 { if len(r.superUser) > 0 {
subjects = append(subjects, rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: r.superUser}) subjects = append(subjects, rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: r.superUser})
} }
errorlist := []error{} errorlist := []error{}
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil { if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(ctx); err != nil {
errorlist = append(errorlist, err) errorlist = append(errorlist, err)
} else { } else {
for _, clusterRoleBinding := range clusterRoleBindings { for _, clusterRoleBinding := range clusterRoleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") rules, err := r.roleToRuleMapper.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, "")
if err != nil { if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive, // 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 // missing a reference is bad, but we can continue with other rolebindings and still have a list
@ -85,12 +87,12 @@ func (r *SubjectAccessEvaluator) AllowedSubjects(requestAttributes authorizer.At
} }
if namespace := requestAttributes.GetNamespace(); len(namespace) > 0 { if namespace := requestAttributes.GetNamespace(); len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil { if roleBindings, err := r.roleBindingLister.ListRoleBindings(ctx, namespace); err != nil {
errorlist = append(errorlist, err) errorlist = append(errorlist, err)
} else { } else {
for _, roleBinding := range roleBindings { for _, roleBinding := range roleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(roleBinding.RoleRef, namespace) rules, err := r.roleToRuleMapper.GetRoleReferenceRules(ctx, roleBinding.RoleRef, namespace)
if err != nil { if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive, // 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 // missing a reference is bad, but we can continue with other rolebindings and still have a list

View File

@ -17,6 +17,7 @@ limitations under the License.
package rbac package rbac
import ( import (
"context"
"reflect" "reflect"
"testing" "testing"
@ -139,7 +140,7 @@ func TestSubjectLocator(t *testing.T) {
ruleResolver, lister := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings) ruleResolver, lister := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := SubjectAccessEvaluator{tt.superUser, lister, lister, ruleResolver} a := SubjectAccessEvaluator{tt.superUser, lister, lister, ruleResolver}
for i, action := range tt.actionsToSubjects { for i, action := range tt.actionsToSubjects {
actualSubjects, err := a.AllowedSubjects(action.action) actualSubjects, err := a.AllowedSubjects(context.Background(), action.action)
if err != nil { if err != nil {
t.Errorf("case %q %d: error %v", tt.name, i, err) t.Errorf("case %q %d: error %v", tt.name, i, err)
} }

View File

@ -92,7 +92,7 @@ func (f AuthorizerFunc) Authorize(ctx context.Context, a Attributes) (Decision,
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace. // RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
type RuleResolver interface { type RuleResolver interface {
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors. // RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error) RulesFor(ctx context.Context, user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
} }
// RequestAttributesGetter provides a function that extracts Attributes from an http.Request // RequestAttributesGetter provides a function that extracts Attributes from an http.Request

View File

@ -33,7 +33,7 @@ func (alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attribu
return authorizer.DecisionAllow, "", nil return authorizer.DecisionAllow, "", nil
} }
func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (alwaysAllowAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
return []authorizer.ResourceRuleInfo{ return []authorizer.ResourceRuleInfo{
&authorizer.DefaultResourceRuleInfo{ &authorizer.DefaultResourceRuleInfo{
Verbs: []string{"*"}, Verbs: []string{"*"},
@ -61,7 +61,7 @@ func (alwaysDenyAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
return authorizer.DecisionNoOpinion, "Everything is forbidden.", nil return authorizer.DecisionNoOpinion, "Everything is forbidden.", nil
} }
func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (alwaysDenyAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, nil return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, nil
} }

View File

@ -77,7 +77,7 @@ func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authoriz
} }
// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful // RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (authzHandler unionAuthzRulesHandler) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var ( var (
errList []error errList []error
resourceRulesList []authorizer.ResourceRuleInfo resourceRulesList []authorizer.ResourceRuleInfo
@ -86,7 +86,7 @@ func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace st
incompleteStatus := false incompleteStatus := false
for _, currAuthzHandler := range authzHandler { for _, currAuthzHandler := range authzHandler {
resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace) resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(ctx, user, namespace)
if incomplete { if incomplete {
incompleteStatus = true incompleteStatus = true

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
) )
type mockAuthzHandler struct { type mockAuthzHandler struct {
@ -86,7 +87,7 @@ type mockAuthzRuleHandler struct {
err error err error
} }
func (mock *mockAuthzRuleHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (mock *mockAuthzRuleHandler) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
if mock.err != nil { if mock.err != nil {
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, mock.err return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, mock.err
} }
@ -150,7 +151,7 @@ func TestAuthorizationResourceRules(t *testing.T) {
authzRulesHandler := NewRuleResolvers(handler1, handler2) authzRulesHandler := NewRuleResolvers(handler1, handler2)
rules, _, _, _ := authzRulesHandler.RulesFor(nil, "") rules, _, _, _ := authzRulesHandler.RulesFor(genericapirequest.NewContext(), nil, "")
actual := getResourceRules(rules) actual := getResourceRules(rules)
if !reflect.DeepEqual(expected, actual) { if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual) t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
@ -189,7 +190,7 @@ func TestAuthorizationNonResourceRules(t *testing.T) {
authzRulesHandler := NewRuleResolvers(handler1, handler2) authzRulesHandler := NewRuleResolvers(handler1, handler2)
_, rules, _, _ := authzRulesHandler.RulesFor(nil, "") _, rules, _, _ := authzRulesHandler.RulesFor(genericapirequest.NewContext(), nil, "")
actual := getNonResourceRules(rules) actual := getNonResourceRules(rules)
if !reflect.DeepEqual(expected, actual) { if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual) t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)

View File

@ -402,7 +402,7 @@ func labelSelectorToAuthorizationAPI(attr authorizer.Attributes) ([]metav1.Label
} }
// TODO: need to finish the method to get the rules when using webhook mode // TODO: need to finish the method to get the rules when using webhook mode
func (w *WebhookAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (w *WebhookAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var ( var (
resourceRules []authorizer.ResourceRuleInfo resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo nonResourceRules []authorizer.NonResourceRuleInfo

View File

@ -27,6 +27,7 @@ import (
"strings" "strings"
"testing" "testing"
corev1 "k8s.io/api/core/v1"
rbacapi "k8s.io/api/rbac/v1" rbacapi "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -40,6 +41,7 @@ import (
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
unionauthz "k8s.io/apiserver/pkg/authorization/union" unionauthz "k8s.io/apiserver/pkg/authorization/union"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
@ -133,26 +135,27 @@ type bootstrapRoles struct {
// //
// client should be authenticated as the RBAC super user. // client should be authenticated as the RBAC super user.
func (b bootstrapRoles) bootstrap(client clientset.Interface) error { func (b bootstrapRoles) bootstrap(client clientset.Interface) error {
ctx := context.TODO()
for _, r := range b.clusterRoles { for _, r := range b.clusterRoles {
_, err := client.RbacV1().ClusterRoles().Create(context.TODO(), &r, metav1.CreateOptions{}) _, err := client.RbacV1().ClusterRoles().Create(ctx, &r, metav1.CreateOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to make request: %v", err) return fmt.Errorf("failed to make request: %v", err)
} }
} }
for _, r := range b.roles { for _, r := range b.roles {
_, err := client.RbacV1().Roles(r.Namespace).Create(context.TODO(), &r, metav1.CreateOptions{}) _, err := client.RbacV1().Roles(r.Namespace).Create(ctx, &r, metav1.CreateOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to make request: %v", err) return fmt.Errorf("failed to make request: %v", err)
} }
} }
for _, r := range b.clusterRoleBindings { for _, r := range b.clusterRoleBindings {
_, err := client.RbacV1().ClusterRoleBindings().Create(context.TODO(), &r, metav1.CreateOptions{}) _, err := client.RbacV1().ClusterRoleBindings().Create(ctx, &r, metav1.CreateOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to make request: %v", err) return fmt.Errorf("failed to make request: %v", err)
} }
} }
for _, r := range b.roleBindings { for _, r := range b.roleBindings {
_, err := client.RbacV1().RoleBindings(r.Namespace).Create(context.TODO(), &r, metav1.CreateOptions{}) _, err := client.RbacV1().RoleBindings(r.Namespace).Create(ctx, &r, metav1.CreateOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to make request: %v", err) return fmt.Errorf("failed to make request: %v", err)
} }
@ -817,3 +820,220 @@ func TestDiscoveryUpgradeBootstrapping(t *testing.T) {
t.Errorf("`system:public-info-viewer` should have inherited Subjects from `system:discovery` Wanted: %v, got %v", newDiscRoleBinding.Subjects, publicInfoViewerRoleBinding.Subjects) t.Errorf("`system:public-info-viewer` should have inherited Subjects from `system:discovery` Wanted: %v, got %v", newDiscRoleBinding.Subjects, publicInfoViewerRoleBinding.Subjects)
} }
} }
type authorizeRequest struct {
ar authorizer.AttributesRecord
expected authorizer.Decision
}
// For 1.31 ctx was wired into the authorizers. This tests check that context values
// are not used inside the code to resolve namespaces or users with the goal of
// preventing regressions in the future.
func TestRBACContextContamination(t *testing.T) {
superUser := "admin/system:masters"
validNamespace := "pod-namespace"
invalidNamespace := "forbidden-namespace"
roles := bootstrapRoles{}
testcases := []authorizeRequest{}
// Tests itself is bit oververbose and each case creates its own objects.
// This makes readability bit easier over trying to overoptimize test case.
// Case 1: clusterrole+clusterbinding
// should allow cluster-scoped request
// should allow namespace-scoped request in any namespace
// should disallow request for resource not in rules
{
roles.clusterRoles = append(roles.clusterRoles, rbacapi.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "c1-clusterrole"},
Rules: []rbacapi.PolicyRule{ruleReadPods},
})
roles.clusterRoleBindings = append(roles.clusterRoleBindings, rbacapi.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{Name: "c1-clusterrolebinding"},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "c1-user"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "c1-clusterrole"},
})
user := &user.DefaultInfo{Name: "c1-user"}
testcases = append(testcases, []authorizeRequest{
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: "", ResourceRequest: true, User: user},
expected: authorizer.DecisionAllow,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: validNamespace, ResourceRequest: true, User: user},
expected: authorizer.DecisionAllow,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "configmaps", Namespace: "", ResourceRequest: true, User: user},
expected: authorizer.DecisionNoOpinion,
},
}...,
)
}
// case 2: clusterrole+rolebinding
// should disallow cluster-scoped request
// should allow namespace-scoped request in rolebinding namespace
// should disallow namespace-scoped request in other namespace
// should disallow request for resource not in rules
{
roles.clusterRoles = append(roles.clusterRoles, rbacapi.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "c2-clusterrole"},
Rules: []rbacapi.PolicyRule{ruleReadPods},
})
roles.roleBindings = append(roles.roleBindings, rbacapi.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "c2-rolebinding",
Namespace: validNamespace,
},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "c2-user"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "c2-clusterrole"},
})
user := &user.DefaultInfo{Name: "c2-user"}
testcases = append(testcases, []authorizeRequest{
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: "", ResourceRequest: true, User: user},
expected: authorizer.DecisionNoOpinion,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: validNamespace, ResourceRequest: true, User: user},
expected: authorizer.DecisionAllow,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "configmaps", Namespace: validNamespace, ResourceRequest: true, User: user},
expected: authorizer.DecisionNoOpinion,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: invalidNamespace, ResourceRequest: true, User: user},
expected: authorizer.DecisionNoOpinion,
},
}...,
)
}
// case 3: role+rolebinding
// should disallow cluster-scoped request
// should allow namespace-scoped request in rolebinding namespace
// should disallow namespace-scoped request in other namespace
// should disallow request for resource not in rules
{
roles.roles = append(roles.roles, rbacapi.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "c3-role",
Namespace: validNamespace,
},
Rules: []rbacapi.PolicyRule{ruleReadPods},
})
roles.roleBindings = append(roles.roleBindings, rbacapi.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "c3-rolebinding",
Namespace: validNamespace,
},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "c3-user"}},
RoleRef: rbacapi.RoleRef{Kind: "Role", Name: "c3-role"},
})
user := &user.DefaultInfo{Name: "c3-user"}
testcases = append(testcases, []authorizeRequest{
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: "", ResourceRequest: true, User: user},
expected: authorizer.DecisionNoOpinion,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: validNamespace, ResourceRequest: true, User: user},
expected: authorizer.DecisionAllow,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "configmaps", Namespace: validNamespace, ResourceRequest: true, User: user},
expected: authorizer.DecisionNoOpinion,
},
{
ar: authorizer.AttributesRecord{Verb: "list", Resource: "pods", Namespace: invalidNamespace, ResourceRequest: true, User: user},
expected: authorizer.DecisionNoOpinion,
},
}...,
)
}
authenticator := group.NewAuthenticatedGroupAdder(bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
superUser: {Name: "admin", Groups: []string{"system:masters"}},
})))
var tearDownAuthorizerFn func()
defer func() {
if tearDownAuthorizerFn != nil {
tearDownAuthorizerFn()
}
}()
var rbacAuthz authorizer.Authorizer
_, kubeConfig, tearDownFn := framework.StartTestServer(context.Background(), t, framework.TestServerSetup{
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
// Also disable namespace lifecycle to workaroung the test limitation that first creates
// roles/rolebindings and only then creates corresponding namespaces.
opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount", "NamespaceLifecycle"}
// Disable built-in authorizers
opts.Authorization.Modes = []string{"AlwaysDeny"}
},
ModifyServerConfig: func(config *controlplane.Config) {
// Append our custom test authenticator
config.ControlPlane.Generic.Authentication.Authenticator = unionauthn.New(config.ControlPlane.Generic.Authentication.Authenticator, authenticator)
// Append our custom test authorizer
rbacAuthz, tearDownAuthorizerFn = newRBACAuthorizer(t, config)
config.ControlPlane.Generic.Authorization.Authorizer = unionauthz.New(config.ControlPlane.Generic.Authorization.Authorizer, rbacAuthz)
},
})
defer tearDownFn()
// Bootstrap the API Server with the test case's initial roles.
superuserClient, _ := clientsetForToken(superUser, kubeConfig)
if err := roles.bootstrap(superuserClient); err != nil {
t.Errorf("failed to apply initial roles: %v", err)
return
}
for _, ns := range []string{validNamespace, invalidNamespace} {
_, err := superuserClient.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
},
}, metav1.CreateOptions{})
if err != nil {
t.Errorf("failed to create namespace: %v", err)
return
}
}
// 3 cases shared for all test cases:
// 1. Default context
// 2. Empty namespace
// 3. Invalid namespace in the context
for j, r := range testcases {
ctx := context.Background()
// 1. Default context
if decision, _, err := rbacAuthz.Authorize(ctx, &r.ar); err != nil {
t.Errorf("req %d: unexpected error: %v", j, err)
return
} else if decision != r.expected {
t.Errorf("req %d: expected %v, got %v", j, r.expected, decision)
}
// 2. Empty namespace
if decision, _, err := rbacAuthz.Authorize(genericapirequest.WithNamespace(ctx, ""), &r.ar); err != nil {
t.Errorf("req %d: unexpected error: %v", j, err)
return
} else if decision != r.expected {
t.Errorf("req %d: expected %v, got %v", j, r.expected, decision)
}
// 3. Invalid namespace in the context
if decision, _, err := rbacAuthz.Authorize(genericapirequest.WithNamespace(ctx, invalidNamespace), &r.ar); err != nil {
t.Errorf("req %d: unexpected error: %v", j, err)
return
} else if decision != r.expected {
t.Errorf("req %d: expected %v, got %v", j, r.expected, decision)
}
}
}