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.
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

View File

@ -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)

View File

@ -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

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{
Status: authorizationapi.SubjectRulesReviewStatus{

View File

@ -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{})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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{})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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")

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
// 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())
}

View File

@ -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

View File

@ -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)
}

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.
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

View File

@ -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
}

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
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

View File

@ -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)

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
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

View File

@ -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)
}
}
}