From 4e4eb8c5c95652b4cbe672a02e4077a93d0bfe2d Mon Sep 17 00:00:00 2001 From: Mangirdas Judeikis Date: Fri, 13 Sep 2024 12:03:47 +0300 Subject: [PATCH] wire in ctx to rbac plugins --- pkg/auth/authorizer/abac/abac.go | 2 +- pkg/auth/authorizer/abac/abac_test.go | 3 +- pkg/kubeapiserver/authorizer/reload.go | 4 +- .../selfsubjectrulesreview/rest.go | 2 +- pkg/registry/rbac/clusterrole/registry.go | 4 +- .../clusterrolebinding/policybased/storage.go | 4 +- .../rbac/clusterrolebinding/registry.go | 4 +- pkg/registry/rbac/role/registry.go | 4 +- .../rbac/rolebinding/policybased/storage.go | 4 +- pkg/registry/rbac/rolebinding/registry.go | 4 +- pkg/registry/rbac/validation/rule.go | 44 ++-- pkg/registry/rbac/validation/rule_test.go | 3 +- .../auth/authorizer/node/node_authorizer.go | 2 +- plugin/pkg/auth/authorizer/rbac/rbac.go | 18 +- .../auth/authorizer/rbac/subject_locator.go | 16 +- .../authorizer/rbac/subject_locator_test.go | 3 +- .../authorization/authorizer/interfaces.go | 2 +- .../authorizerfactory/builtin.go | 4 +- .../pkg/authorization/union/union.go | 4 +- .../pkg/authorization/union/union_test.go | 7 +- .../plugin/pkg/authorizer/webhook/webhook.go | 2 +- test/integration/auth/rbac_test.go | 228 +++++++++++++++++- 22 files changed, 297 insertions(+), 71 deletions(-) diff --git a/pkg/auth/authorizer/abac/abac.go b/pkg/auth/authorizer/abac/abac.go index b69c14419b3..2dd116d3d06 100644 --- a/pkg/auth/authorizer/abac/abac.go +++ b/pkg/auth/authorizer/abac/abac.go @@ -239,7 +239,7 @@ func (pl PolicyList) Authorize(ctx context.Context, a authorizer.Attributes) (au } // 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 ( resourceRules []authorizer.ResourceRuleInfo nonResourceRules []authorizer.NonResourceRuleInfo diff --git a/pkg/auth/authorizer/abac/abac_test.go b/pkg/auth/authorizer/abac/abac_test.go index 129fa03acaf..8b8d470c7f0 100644 --- a/pkg/auth/authorizer/abac/abac_test.go +++ b/pkg/auth/authorizer/abac/abac_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authentication/user" "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/v0" "k8s.io/kubernetes/pkg/apis/abac/v1beta1" @@ -324,7 +325,7 @@ func TestRulesFor(t *testing.T) { User: &tc.User, Namespace: tc.Namespace, } - resourceRules, nonResourceRules, _, _ := a.RulesFor(attr.GetUser(), attr.GetNamespace()) + resourceRules, nonResourceRules, _, _ := a.RulesFor(genericapirequest.NewContext(), attr.GetUser(), attr.GetNamespace()) actualResourceRules := getResourceRules(resourceRules) if !reflect.DeepEqual(tc.ExpectResourceRules, actualResourceRules) { t.Logf("tc: %v -> attr %v", tc, attr) diff --git a/pkg/kubeapiserver/authorizer/reload.go b/pkg/kubeapiserver/authorizer/reload.go index afd3ca76be6..c5b53dfb06d 100644 --- a/pkg/kubeapiserver/authorizer/reload.go +++ b/pkg/kubeapiserver/authorizer/reload.go @@ -78,8 +78,8 @@ func (r *reloadableAuthorizerResolver) Authorize(ctx context.Context, a authoriz return r.current.Load().authorizer.Authorize(ctx, a) } -func (r *reloadableAuthorizerResolver) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { - return r.current.Load().ruleResolver.RulesFor(user, namespace) +func (r *reloadableAuthorizerResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { + return r.current.Load().ruleResolver.RulesFor(ctx, user, namespace) } // newForConfig constructs diff --git a/pkg/registry/authorization/selfsubjectrulesreview/rest.go b/pkg/registry/authorization/selfsubjectrulesreview/rest.go index 2065d2897fd..cf734550a98 100644 --- a/pkg/registry/authorization/selfsubjectrulesreview/rest.go +++ b/pkg/registry/authorization/selfsubjectrulesreview/rest.go @@ -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{ Status: authorizationapi.SubjectRulesReviewStatus{ diff --git a/pkg/registry/rbac/clusterrole/registry.go b/pkg/registry/rbac/clusterrole/registry.go index 1c572d1af98..a9719162bdb 100644 --- a/pkg/registry/rbac/clusterrole/registry.go +++ b/pkg/registry/rbac/clusterrole/registry.go @@ -62,6 +62,6 @@ type AuthorizerAdapter struct { } // GetClusterRole returns the corresponding ClusterRole by name -func (a AuthorizerAdapter) GetClusterRole(name string) (*rbacv1.ClusterRole, error) { - return a.Registry.GetClusterRole(genericapirequest.NewContext(), name, &metav1.GetOptions{}) +func (a AuthorizerAdapter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { + return a.Registry.GetClusterRole(genericapirequest.WithNamespace(ctx, ""), name, &metav1.GetOptions{}) } diff --git a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go index 3e4b9d725f5..95202ca1f1c 100644 --- a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go +++ b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go @@ -81,7 +81,7 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati if err != nil { return nil, err } - rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, metav1.NamespaceNone) + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, metav1.NamespaceNone) if err != nil { return nil, err } @@ -115,7 +115,7 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec if err != nil { return nil, err } - rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, metav1.NamespaceNone) + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, metav1.NamespaceNone) if err != nil { return nil, err } diff --git a/pkg/registry/rbac/clusterrolebinding/registry.go b/pkg/registry/rbac/clusterrolebinding/registry.go index 34f5b73a9ed..3e333d2b344 100644 --- a/pkg/registry/rbac/clusterrolebinding/registry.go +++ b/pkg/registry/rbac/clusterrolebinding/registry.go @@ -61,8 +61,8 @@ type AuthorizerAdapter struct { Registry Registry } -func (a AuthorizerAdapter) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) { - list, err := a.Registry.ListClusterRoleBindings(genericapirequest.NewContext(), &metainternalversion.ListOptions{}) +func (a AuthorizerAdapter) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { + list, err := a.Registry.ListClusterRoleBindings(genericapirequest.WithNamespace(ctx, ""), &metainternalversion.ListOptions{}) if err != nil { return nil, err } diff --git a/pkg/registry/rbac/role/registry.go b/pkg/registry/rbac/role/registry.go index 4df0f14bb34..19e2d21c265 100644 --- a/pkg/registry/rbac/role/registry.go +++ b/pkg/registry/rbac/role/registry.go @@ -62,6 +62,6 @@ type AuthorizerAdapter struct { } // GetRole returns the corresponding Role by name in specified namespace -func (a AuthorizerAdapter) GetRole(namespace, name string) (*rbacv1.Role, error) { - return a.Registry.GetRole(genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace), name, &metav1.GetOptions{}) +func (a AuthorizerAdapter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { + return a.Registry.GetRole(genericapirequest.WithNamespace(ctx, namespace), name, &metav1.GetOptions{}) } diff --git a/pkg/registry/rbac/rolebinding/policybased/storage.go b/pkg/registry/rbac/rolebinding/policybased/storage.go index 21171ec4022..b4fabfad161 100644 --- a/pkg/registry/rbac/rolebinding/policybased/storage.go +++ b/pkg/registry/rbac/rolebinding/policybased/storage.go @@ -89,7 +89,7 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati if err != nil { return nil, err } - rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, namespace) + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, namespace) if err != nil { return nil, err } @@ -130,7 +130,7 @@ func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjec if err != nil { return nil, err } - rules, err := s.ruleResolver.GetRoleReferenceRules(v1RoleRef, namespace) + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, v1RoleRef, namespace) if err != nil { return nil, err } diff --git a/pkg/registry/rbac/rolebinding/registry.go b/pkg/registry/rbac/rolebinding/registry.go index af78c1a4999..1ff5b3f43ef 100644 --- a/pkg/registry/rbac/rolebinding/registry.go +++ b/pkg/registry/rbac/rolebinding/registry.go @@ -61,8 +61,8 @@ type AuthorizerAdapter struct { Registry Registry } -func (a AuthorizerAdapter) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) { - list, err := a.Registry.ListRoleBindings(genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace), &metainternalversion.ListOptions{}) +func (a AuthorizerAdapter) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { + list, err := a.Registry.ListRoleBindings(genericapirequest.WithNamespace(ctx, namespace), &metainternalversion.ListOptions{}) if err != nil { return nil, err } diff --git a/pkg/registry/rbac/validation/rule.go b/pkg/registry/rbac/validation/rule.go index 603f56afbc1..5322c7419fe 100644 --- a/pkg/registry/rbac/validation/rule.go +++ b/pkg/registry/rbac/validation/rule.go @@ -37,16 +37,16 @@ import ( type AuthorizationRuleResolver 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 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 // 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. - 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. // 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. @@ -59,7 +59,7 @@ func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleReso } namespace, _ := genericapirequest.NamespaceFrom(ctx) - ownerRules, err := ruleResolver.RulesFor(user, namespace) + ownerRules, err := ruleResolver.RulesFor(ctx, user, namespace) 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. 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 { - GetRole(namespace, name string) (*rbacv1.Role, error) + GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) } type RoleBindingLister interface { - ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) + ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) } type ClusterRoleGetter interface { - GetClusterRole(name string) (*rbacv1.ClusterRole, error) + GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) } 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{} - r.VisitRulesFor(user, namespace, visitor.visit) + r.VisitRulesFor(ctx, user, namespace, visitor.visit) 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) { - if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil { +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(ctx); err != nil { if !visitor(nil, nil, err) { return } @@ -188,7 +188,7 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi if !applies { continue } - rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") + rules, err := r.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, "") if err != nil { if !visitor(nil, nil, err) { return @@ -206,7 +206,7 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi } 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) { return } @@ -217,7 +217,7 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi if !applies { continue } - rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace) + rules, err := r.GetRoleReferenceRules(ctx, roleBinding.RoleRef, namespace) if err != nil { if !visitor(nil, nil, err) { return @@ -237,17 +237,17 @@ func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, vi } // 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 { case "Role": - role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name) + role, err := r.roleGetter.GetRole(ctx, bindingNamespace, roleRef.Name) if err != nil { return nil, err } return role.Rules, nil case "ClusterRole": - clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name) + clusterRole, err := r.clusterRoleGetter.GetClusterRole(ctx, roleRef.Name) if err != nil { return nil, err } @@ -326,7 +326,7 @@ type StaticRoles struct { 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 { 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") } -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 { if clusterRole.Name == name { return clusterRole, nil @@ -347,7 +347,7 @@ func (r *StaticRoles) GetClusterRole(name string) (*rbacv1.ClusterRole, error) { 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 { 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 } -func (r *StaticRoles) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) { +func (r *StaticRoles) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { return r.clusterRoleBindings, nil } diff --git a/pkg/registry/rbac/validation/rule_test.go b/pkg/registry/rbac/validation/rule_test.go index 01c24c6b734..459d9c21ae5 100644 --- a/pkg/registry/rbac/validation/rule_test.go +++ b/pkg/registry/rbac/validation/rule_test.go @@ -27,6 +27,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "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 @@ -145,7 +146,7 @@ func TestDefaultRuleResolver(t *testing.T) { for i, tc := range tests { 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 { t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err) continue diff --git a/plugin/pkg/auth/authorizer/node/node_authorizer.go b/plugin/pkg/auth/authorizer/node/node_authorizer.go index 93747786b0b..2a1be5b56cc 100644 --- a/plugin/pkg/auth/authorizer/node/node_authorizer.go +++ b/plugin/pkg/auth/authorizer/node/node_authorizer.go @@ -95,7 +95,7 @@ var ( 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 { // indicate nodes do not have fully enumerated permissions return nil, nil, true, fmt.Errorf("node authorizer does not support user rule resolution") diff --git a/plugin/pkg/auth/authorizer/rbac/rbac.go b/plugin/pkg/auth/authorizer/rbac/rbac.go index d5087309f57..27f5946dd83 100644 --- a/plugin/pkg/auth/authorizer/rbac/rbac.go +++ b/plugin/pkg/auth/authorizer/rbac/rbac.go @@ -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 // supplied, you do not have to fail the request. If you cannot, you should indicate the error along // 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, // and each error encountered resolving those rules. Rule may be nil if err is non-nil. // 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 { @@ -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) { 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 { return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil } @@ -126,13 +126,13 @@ func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes author 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 ( resourceRules []authorizer.ResourceRuleInfo nonResourceRules []authorizer.NonResourceRuleInfo ) - policyRules, err := r.authorizationRuleResolver.RulesFor(user, namespace) + policyRules, err := r.authorizationRuleResolver.RulesFor(ctx, user, namespace) for _, policyRule := range policyRules { if len(policyRule.Resources) > 0 { r := authorizer.DefaultResourceRuleInfo{ @@ -196,7 +196,7 @@ type RoleGetter struct { 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) } @@ -204,7 +204,7 @@ type RoleBindingLister struct { 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()) } @@ -212,7 +212,7 @@ type ClusterRoleGetter struct { 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) } @@ -220,6 +220,6 @@ type ClusterRoleBindingLister struct { 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()) } diff --git a/plugin/pkg/auth/authorizer/rbac/subject_locator.go b/plugin/pkg/auth/authorizer/rbac/subject_locator.go index cdd327e5b7c..c4947de6a08 100644 --- a/plugin/pkg/auth/authorizer/rbac/subject_locator.go +++ b/plugin/pkg/auth/authorizer/rbac/subject_locator.go @@ -18,6 +18,8 @@ limitations under the License. package rbac import ( + "context" + rbacv1 "k8s.io/api/rbac/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/authentication/user" @@ -28,11 +30,11 @@ import ( 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 rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) + GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) } type SubjectLocator interface { - AllowedSubjects(attributes authorizer.Attributes) ([]rbacv1.Subject, error) + AllowedSubjects(ctx context.Context, attributes authorizer.Attributes) ([]rbacv1.Subject, error) } 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. // 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}} if len(r.superUser) > 0 { subjects = append(subjects, rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: r.superUser}) } errorlist := []error{} - if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil { + if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(ctx); err != nil { errorlist = append(errorlist, err) } else { for _, clusterRoleBinding := range clusterRoleBindings { - rules, err := r.roleToRuleMapper.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") + rules, err := r.roleToRuleMapper.GetRoleReferenceRules(ctx, 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 @@ -85,12 +87,12 @@ func (r *SubjectAccessEvaluator) AllowedSubjects(requestAttributes authorizer.At } 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) } else { 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 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 diff --git a/plugin/pkg/auth/authorizer/rbac/subject_locator_test.go b/plugin/pkg/auth/authorizer/rbac/subject_locator_test.go index d798494885a..97c3ce94f55 100644 --- a/plugin/pkg/auth/authorizer/rbac/subject_locator_test.go +++ b/plugin/pkg/auth/authorizer/rbac/subject_locator_test.go @@ -17,6 +17,7 @@ limitations under the License. package rbac import ( + "context" "reflect" "testing" @@ -139,7 +140,7 @@ func TestSubjectLocator(t *testing.T) { ruleResolver, lister := rbacregistryvalidation.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) + actualSubjects, err := a.AllowedSubjects(context.Background(), action.action) if err != nil { t.Errorf("case %q %d: error %v", tt.name, i, err) } diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go index d39deb17eb2..2f5f65e2288 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go @@ -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. type RuleResolver interface { // 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 diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go index 6fe3fa96ed8..b3b1f09a6fd 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go @@ -33,7 +33,7 @@ func (alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attribu 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{ &authorizer.DefaultResourceRuleInfo{ Verbs: []string{"*"}, @@ -61,7 +61,7 @@ func (alwaysDenyAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut 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 } diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go b/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go index 460d9a4ab57..0e5007cfa2e 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go @@ -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 -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 ( errList []error resourceRulesList []authorizer.ResourceRuleInfo @@ -86,7 +86,7 @@ func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace st incompleteStatus := false for _, currAuthzHandler := range authzHandler { - resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace) + resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(ctx, user, namespace) if incomplete { incompleteStatus = true diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go b/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go index 057c1cefe1e..c8b467866b7 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" ) type mockAuthzHandler struct { @@ -86,7 +87,7 @@ type mockAuthzRuleHandler struct { 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 { return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, mock.err } @@ -150,7 +151,7 @@ func TestAuthorizationResourceRules(t *testing.T) { authzRulesHandler := NewRuleResolvers(handler1, handler2) - rules, _, _, _ := authzRulesHandler.RulesFor(nil, "") + rules, _, _, _ := authzRulesHandler.RulesFor(genericapirequest.NewContext(), nil, "") actual := getResourceRules(rules) if !reflect.DeepEqual(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) - _, rules, _, _ := authzRulesHandler.RulesFor(nil, "") + _, rules, _, _ := authzRulesHandler.RulesFor(genericapirequest.NewContext(), nil, "") actual := getNonResourceRules(rules) if !reflect.DeepEqual(expected, actual) { t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual) diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go index ebc4949d920..f70cce6e15c 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go @@ -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 -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 ( resourceRules []authorizer.ResourceRuleInfo nonResourceRules []authorizer.NonResourceRuleInfo diff --git a/test/integration/auth/rbac_test.go b/test/integration/auth/rbac_test.go index f24067e5fc9..fcfae435196 100644 --- a/test/integration/auth/rbac_test.go +++ b/test/integration/auth/rbac_test.go @@ -27,6 +27,7 @@ import ( "strings" "testing" + corev1 "k8s.io/api/core/v1" rbacapi "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -40,6 +41,7 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" unionauthz "k8s.io/apiserver/pkg/authorization/union" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" @@ -133,26 +135,27 @@ type bootstrapRoles struct { // // client should be authenticated as the RBAC super user. func (b bootstrapRoles) bootstrap(client clientset.Interface) error { + ctx := context.TODO() 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 { return fmt.Errorf("failed to make request: %v", err) } } 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 { return fmt.Errorf("failed to make request: %v", err) } } 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 { return fmt.Errorf("failed to make request: %v", err) } } 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 { 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) } } + +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) + } + + } +}