Skip to content

Commit

Permalink
aggregate rule resolvers in GRBRuleResolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
raulcabello committed Apr 23, 2024
1 parent adf0fb7 commit 36016dc
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,26 @@ const (
localCluster = "local"
)

// GRBClusterRuleResolver implements the rbacv1.AuthorizationRuleResolver interface. Provides rule resolution
// GRBRuleResolvers contains three rule resolvers for: InheritedClusterRules, FleetWorkspaceRules, FleetWorkspaceVerbs.
type GRBRuleResolvers struct {
ICRResolver *GRBRuleResolver
FWRulesResolver *GRBRuleResolver
FWVerbsResolver *GRBRuleResolver
}

// GRBRuleResolver implements the rbacv1.AuthorizationRuleResolver interface. Provides rule resolution
// for the permissions a GRB gives that apply in a given cluster (or all clusters).
type GRBClusterRuleResolver struct {
type GRBRuleResolver struct {
gbrCache v3.GlobalRoleBindingCache
grResolver *auth.GlobalRoleResolver
ruleResolver func(namespace string, gr *apisv3.GlobalRole, grResolver *auth.GlobalRoleResolver) ([]rbacv1.PolicyRule, error)
}

// NewGRRuleResolvers returns resolvers for resolving rules given through GlobalRoleBindings
// which apply to cluster(s). This function can only be called once for each unique instance of GlobalRoleBindings.
func NewGRRuleResolvers(grbCache v3.GlobalRoleBindingCache, grResolver *auth.GlobalRoleResolver) (*GRBClusterRuleResolver, *GRBClusterRuleResolver, *GRBClusterRuleResolver) {
// NewGRBRuleResolvers returns resolvers for resolving rules given through GlobalRoleBindings
// which apply to cluster(s). This function can only be called once for each unique instance of grbCache.
func NewGRBRuleResolvers(grbCache v3.GlobalRoleBindingCache, grResolver *auth.GlobalRoleResolver) *GRBRuleResolvers {
grbCache.AddIndexer(grbSubjectIndex, grbBySubject)
inheritedClusterRoleResolver := &GRBClusterRuleResolver{
inheritedClusterRoleResolver := &GRBRuleResolver{
gbrCache: grbCache,
grResolver: grResolver,
ruleResolver: func(namespace string, gr *apisv3.GlobalRole, grResolver *auth.GlobalRoleResolver) ([]rbacv1.PolicyRule, error) {
Expand All @@ -42,48 +49,52 @@ func NewGRRuleResolvers(grbCache v3.GlobalRoleBindingCache, grResolver *auth.Glo
return rules, err
},
}
fleetWorkspaceResourceRulesResolver := &GRBClusterRuleResolver{
fleetWorkspaceResourceRulesResolver := &GRBRuleResolver{
gbrCache: grbCache,
grResolver: grResolver,
ruleResolver: func(_ string, gr *apisv3.GlobalRole, grResolver *auth.GlobalRoleResolver) ([]rbacv1.PolicyRule, error) {
return grResolver.FleetWorkspacePermissionsResourceRulesFromRole(gr), nil
},
}
fleetWorkspaceVerbsResolver := &GRBClusterRuleResolver{
fleetWorkspaceVerbsResolver := &GRBRuleResolver{
gbrCache: grbCache,
grResolver: grResolver,
ruleResolver: func(_ string, gr *apisv3.GlobalRole, grResolver *auth.GlobalRoleResolver) ([]rbacv1.PolicyRule, error) {
return grResolver.FleetWorkspacePermissionsWorkspaceVerbsFromRole(gr), nil
},
}

return inheritedClusterRoleResolver, fleetWorkspaceResourceRulesResolver, fleetWorkspaceVerbsResolver
return &GRBRuleResolvers{
ICRResolver: inheritedClusterRoleResolver,
FWVerbsResolver: fleetWorkspaceVerbsResolver,
FWRulesResolver: fleetWorkspaceResourceRulesResolver,
}
}

// GetRoleReferenceRules is used to find which rules are granted by a rolebinding/clusterRoleBinding. Since we don't
// use these primitives to refer to the globalRoles, this function returns an empty slice.
func (g *GRBClusterRuleResolver) GetRoleReferenceRules(rbacv1.RoleRef, string) ([]rbacv1.PolicyRule, error) {
func (g *GRBRuleResolver) GetRoleReferenceRules(rbacv1.RoleRef, string) ([]rbacv1.PolicyRule, error) {
return []rbacv1.PolicyRule{}, nil
}

// RulesFor returns the list of Cluster rules that apply in a given namespace (usually either the namespace of a
// specific cluster or "" for all clusters). If an error is returned, the slice of PolicyRules may not be complete,
// but contains all retrievable rules.
func (g *GRBClusterRuleResolver) RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error) {
func (g *GRBRuleResolver) RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error) {
visitor := &ruleAccumulator{}
g.visitRulesForWithRuleResolver(user, namespace, visitor.visit, g.ruleResolver)
return visitor.rules, visitor.getError()
}

// 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. This will return different rules for the "local" namespace.
func (g *GRBClusterRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
func (g *GRBRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
g.visitRulesForWithRuleResolver(user, namespace, visitor, g.ruleResolver)
}

// visitRulesForWithRuleResolver 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. This will return different rules for the "local" namespace.
func (g *GRBClusterRuleResolver) visitRulesForWithRuleResolver(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool, ruleResolver func(namespace string, gr *apisv3.GlobalRole, grResolver *auth.GlobalRoleResolver) ([]rbacv1.PolicyRule, error)) {
func (g *GRBRuleResolver) visitRulesForWithRuleResolver(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool, ruleResolver func(namespace string, gr *apisv3.GlobalRole, grResolver *auth.GlobalRoleResolver) ([]rbacv1.PolicyRule, error)) {
var grbs []*apisv3.GlobalRoleBinding
// gather all grbs that apply to this user through group or user assignment
for _, group := range user.GetGroups() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,9 @@ func (g *GRBClusterRuleResolverSuite) TestGRBClusterRuleResolver() {
}

grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCache, nil), state.grCache)
grbResolver, _, _ := NewGRRuleResolvers(state.grbCache, grResolver)
grbResolvers := NewGRBRuleResolvers(state.grbCache, grResolver)

rules, err := grbResolver.RulesFor(g.userInfo, test.namespace)
rules, err := grbResolvers.ICRResolver.RulesFor(g.userInfo, test.namespace)
g.Require().Len(rules, len(test.wantRules))
for _, rule := range test.wantRules {
g.Require().Contains(rules, rule)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,6 @@ func (m *testState) createBaseGRResolver() *auth.GlobalRoleResolver {
return auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(m.rtCacheMock, nil), m.grCacheMock)
}

func (m *testState) createBaseGRBResolvers(grResolver *auth.GlobalRoleResolver) (*resolvers.GRBClusterRuleResolver, *resolvers.GRBClusterRuleResolver, *resolvers.GRBClusterRuleResolver) {
return resolvers.NewGRRuleResolvers(m.grbCacheMock, grResolver)
func (m *testState) createBaseGRBResolvers(grResolver *auth.GlobalRoleResolver) *resolvers.GRBRuleResolvers {
return resolvers.NewGRBRuleResolvers(m.grbCacheMock, grResolver)
}
29 changes: 12 additions & 17 deletions pkg/resources/management.cattle.io/v3/globalrole/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,13 @@ const (
)

// NewValidator returns a new validator used for validation globalRoles.
func NewValidator(ruleResolver validation.AuthorizationRuleResolver, icrResolver *resolvers.GRBClusterRuleResolver, fwResolver *resolvers.GRBClusterRuleResolver,
fwVerbsResolver *resolvers.GRBClusterRuleResolver, sar authorizationv1.SubjectAccessReviewInterface, grResolver *auth.GlobalRoleResolver) *Validator {
func NewValidator(ruleResolver validation.AuthorizationRuleResolver, grbResolvers *resolvers.GRBRuleResolvers, sar authorizationv1.SubjectAccessReviewInterface, grResolver *auth.GlobalRoleResolver) *Validator {
return &Validator{
admitter: admitter{
resolver: ruleResolver,
grResolver: grResolver,
icrResolver: icrResolver,
fwRulesResolver: fwResolver,
fwVerbsResolver: fwVerbsResolver,
sar: sar,
resolver: ruleResolver,
grResolver: grResolver,
grbResolvers: grbResolvers,
sar: sar,
},
}
}
Expand Down Expand Up @@ -74,12 +71,10 @@ func (v *Validator) Admitters() []admission.Admitter {
}

type admitter struct {
resolver validation.AuthorizationRuleResolver
grResolver *auth.GlobalRoleResolver
icrResolver *resolvers.GRBClusterRuleResolver
fwRulesResolver *resolvers.GRBClusterRuleResolver
fwVerbsResolver *resolvers.GRBClusterRuleResolver
sar authorizationv1.SubjectAccessReviewInterface
resolver validation.AuthorizationRuleResolver
grResolver *auth.GlobalRoleResolver
grbResolvers *resolvers.GRBRuleResolvers
sar authorizationv1.SubjectAccessReviewInterface
}

// Admit is the entrypoint for the validator. Admit will return an error if it's unable to process the request.
Expand Down Expand Up @@ -161,19 +156,19 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp
fwWorkspaceVerbsRules := a.grResolver.FleetWorkspacePermissionsWorkspaceVerbsFromRole(newGR)

escalateChecker := common.NewCachedVerbChecker(request, newGR.Name, a.sar, gvr, escalateVerb)
returnError = errors.Join(returnError, escalateChecker.IsRulesAllowed(clusterRules, a.icrResolver, ""))
returnError = errors.Join(returnError, escalateChecker.IsRulesAllowed(clusterRules, a.grbResolvers.ICRResolver, ""))
if escalateChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
returnError = errors.Join(returnError, escalateChecker.IsRulesAllowed(globalRules, a.resolver, ""))
if escalateChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
returnError = errors.Join(returnError, escalateChecker.IsRulesAllowed(fwResourceRules, a.fwRulesResolver, ""))
returnError = errors.Join(returnError, escalateChecker.IsRulesAllowed(fwResourceRules, a.grbResolvers.FWRulesResolver, ""))
if escalateChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
returnError = errors.Join(returnError, escalateChecker.IsRulesAllowed(fwWorkspaceVerbsRules, a.fwVerbsResolver, ""))
returnError = errors.Join(returnError, escalateChecker.IsRulesAllowed(fwWorkspaceVerbsRules, a.grbResolvers.FWVerbsResolver, ""))
if escalateChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -865,8 +865,8 @@ func TestAdmit(t *testing.T) {
test.args.stateSetup(state)
}
grResolver := state.createBaseGRResolver()
icrResolver, fwResolver, fwVerbsResolver := state.createBaseGRBResolvers(grResolver)
admitters := globalrole.NewValidator(state.resolver, icrResolver, fwResolver, fwVerbsResolver, state.sarMock, grResolver).Admitters()
grbResolvers := state.createBaseGRBResolvers(grResolver)
admitters := globalrole.NewValidator(state.resolver, grbResolvers, state.sarMock, grResolver).Admitters()
assert.Len(t, admitters, 1)

req := createGRRequest(t, test)
Expand All @@ -884,7 +884,7 @@ func TestAdmit(t *testing.T) {
func Test_UnexpectedErrors(t *testing.T) {
t.Parallel()
resolver, _ := validation.NewTestRuleResolver(nil, nil, nil, nil)
validator := globalrole.NewValidator(resolver, nil, nil, nil, nil, nil)
validator := globalrole.NewValidator(resolver, nil, nil, nil)
admitters := validator.Admitters()
require.Len(t, admitters, 1, "wanted only one admitter")
test := testCase{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,14 @@ var (
const bindVerb = "bind"

// NewValidator returns a new validator for GlobalRoleBindings.
func NewValidator(resolver rbacvalidation.AuthorizationRuleResolver, icrResolver *resolvers.GRBClusterRuleResolver,
fwRulesResolver *resolvers.GRBClusterRuleResolver, fwVerbsResolver *resolvers.GRBClusterRuleResolver,
func NewValidator(resolver rbacvalidation.AuthorizationRuleResolver, grbResolvers *resolvers.GRBRuleResolvers,
sar authorizationv1.SubjectAccessReviewInterface, grResolver *auth.GlobalRoleResolver) *Validator {
return &Validator{
admitter: admitter{
resolver: resolver,
icrResolver: icrResolver,
fwRulesResolver: fwRulesResolver,
fwVerbsResolver: fwVerbsResolver,
sar: sar,
grResolver: grResolver,
resolver: resolver,
grbResolvers: grbResolvers,
sar: sar,
grResolver: grResolver,
},
}
}
Expand Down Expand Up @@ -79,12 +76,10 @@ func (v *Validator) Admitters() []admission.Admitter {
}

type admitter struct {
resolver rbacvalidation.AuthorizationRuleResolver
icrResolver *resolvers.GRBClusterRuleResolver
fwRulesResolver *resolvers.GRBClusterRuleResolver
fwVerbsResolver *resolvers.GRBClusterRuleResolver
grResolver *auth.GlobalRoleResolver
sar authorizationv1.SubjectAccessReviewInterface
resolver rbacvalidation.AuthorizationRuleResolver
grbResolvers *resolvers.GRBRuleResolvers
grResolver *auth.GlobalRoleResolver
sar authorizationv1.SubjectAccessReviewInterface
}

// Admit handles the webhook admission request sent to this webhook.
Expand Down Expand Up @@ -145,19 +140,19 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp
var returnError error
bindChecker := common.NewCachedVerbChecker(request, globalRole.Name, a.sar, globalRoleGvr, bindVerb)

returnError = bindChecker.IsRulesAllowed(clusterRules, a.icrResolver, "")
returnError = bindChecker.IsRulesAllowed(clusterRules, a.grbResolvers.ICRResolver, "")
if bindChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
returnError = errors.Join(returnError, bindChecker.IsRulesAllowed(globalRules, a.resolver, ""))
if bindChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
returnError = errors.Join(returnError, bindChecker.IsRulesAllowed(fwResourceRules, a.fwRulesResolver, ""))
returnError = errors.Join(returnError, bindChecker.IsRulesAllowed(fwResourceRules, a.grbResolvers.FWRulesResolver, ""))
if bindChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
returnError = errors.Join(returnError, bindChecker.IsRulesAllowed(fwWorkspaceVerbsRules, a.fwVerbsResolver, ""))
returnError = errors.Join(returnError, bindChecker.IsRulesAllowed(fwWorkspaceVerbsRules, a.grbResolvers.FWVerbsResolver, ""))
if bindChecker.HasVerb() {
return admission.ResponseAllowed(), nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -795,8 +795,8 @@ func TestAdmit(t *testing.T) {
test.args.stateSetup(state)
}
grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil), state.grCacheMock)
icrResolver, fwRulesResolver, fwVerbsResolver := resolvers.NewGRRuleResolvers(state.grbCacheMock, grResolver)
admitters := globalrolebinding.NewValidator(state.resolver, icrResolver, fwRulesResolver, fwVerbsResolver, state.sarMock, grResolver).Admitters()
gbrResolvers := resolvers.NewGRBRuleResolvers(state.grbCacheMock, grResolver)
admitters := globalrolebinding.NewValidator(state.resolver, gbrResolvers, state.sarMock, grResolver).Admitters()
require.Len(t, admitters, 1)

req := createGRBRequest(t, test)
Expand All @@ -816,8 +816,8 @@ func Test_UnexpectedErrors(t *testing.T) {
t.Parallel()
state := newDefaultState(t)
grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil), state.grCacheMock)
icrResolver, fwRulesResolver, fwVerbsResolver := resolvers.NewGRRuleResolvers(state.grbCacheMock, grResolver)
validator := globalrolebinding.NewValidator(state.resolver, icrResolver, fwRulesResolver, fwVerbsResolver, state.sarMock, grResolver)
gbrResolvers := resolvers.NewGRBRuleResolvers(state.grbCacheMock, grResolver)
validator := globalrolebinding.NewValidator(state.resolver, gbrResolvers, state.sarMock, grResolver)
admitters := validator.Admitters()
require.Len(t, admitters, 1, "wanted only one admitter")
admitter := admitters[0]
Expand Down
6 changes: 3 additions & 3 deletions pkg/server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ func Validation(clients *clients.Clients) ([]admission.ValidatingAdmissionHandle
clusterProxyConfigs := clusterproxyconfig.NewValidator(clients.Management.ClusterProxyConfig().Cache())
crtbResolver := resolvers.NewCRTBRuleResolver(clients.Management.ClusterRoleTemplateBinding().Cache(), clients.RoleTemplateResolver)
prtbResolver := resolvers.NewPRTBRuleResolver(clients.Management.ProjectRoleTemplateBinding().Cache(), clients.RoleTemplateResolver)
icrResolver, fwRulesResolver, fwVerbsResolver := resolvers.NewGRRuleResolvers(clients.Management.GlobalRoleBinding().Cache(), clients.GlobalRoleResolver)
grbResolvers := resolvers.NewGRBRuleResolvers(clients.Management.GlobalRoleBinding().Cache(), clients.GlobalRoleResolver)
psact := podsecurityadmissionconfigurationtemplate.NewValidator(clients.Management.Cluster().Cache(), clients.Provisioning.Cluster().Cache())
globalRoles := globalrole.NewValidator(clients.DefaultResolver, icrResolver, fwRulesResolver, fwVerbsResolver, clients.K8s.AuthorizationV1().SubjectAccessReviews(), clients.GlobalRoleResolver)
globalRoleBindings := globalrolebinding.NewValidator(clients.DefaultResolver, icrResolver, fwRulesResolver, fwVerbsResolver, clients.K8s.AuthorizationV1().SubjectAccessReviews(), clients.GlobalRoleResolver)
globalRoles := globalrole.NewValidator(clients.DefaultResolver, grbResolvers, clients.K8s.AuthorizationV1().SubjectAccessReviews(), clients.GlobalRoleResolver)
globalRoleBindings := globalrolebinding.NewValidator(clients.DefaultResolver, grbResolvers, clients.K8s.AuthorizationV1().SubjectAccessReviews(), clients.GlobalRoleResolver)
prtbs := projectroletemplatebinding.NewValidator(prtbResolver, crtbResolver, clients.DefaultResolver, clients.RoleTemplateResolver, clients.Management.Cluster().Cache(), clients.Management.Project().Cache())
crtbs := clusterroletemplatebinding.NewValidator(crtbResolver, clients.DefaultResolver, clients.RoleTemplateResolver, clients.Management.GlobalRoleBinding().Cache(), clients.Management.Cluster().Cache())
roleTemplates := roletemplate.NewValidator(clients.DefaultResolver, clients.RoleTemplateResolver, clients.K8s.AuthorizationV1().SubjectAccessReviews(), clients.Management.GlobalRole().Cache())
Expand Down

0 comments on commit 36016dc

Please sign in to comment.