Skip to content

Commit

Permalink
Adds ruleResolvers for PRTBs and CRTBs.
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinJoiner committed Sep 19, 2022
1 parent 2619688 commit f3573f6
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 150 deletions.
48 changes: 48 additions & 0 deletions pkg/resolvers/aggregateResolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package resolvers

import (
"fmt"

rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/kubernetes/pkg/registry/rbac/validation"
)

// AggregateRuleResolver conforms to the rbac/validation.AuthorizationRuleResolver interface and is used to aggregate multiple other AuthorizationRuleResolver into one resolver.
type AggregateRuleResolver struct {
resolvers []validation.AuthorizationRuleResolver
}

// NewAggregateRuleResolver creates a new AggregateRuleResolver that will combine the outputs of all resolvers provided.
func NewAggregateRuleResolver(resolvers ...validation.AuthorizationRuleResolver) *AggregateRuleResolver {
return &AggregateRuleResolver{
resolvers: resolvers,
}
}

// GetRoleReferenceRules calls GetRoleReferenceRules on each resolver and returns all returned rules and errors.
func (a *AggregateRuleResolver) GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) {
visitor := &ruleAccumulator{}
for _, resolver := range a.resolvers {
rules, err := resolver.GetRoleReferenceRules(roleRef, namespace)
visitRules(nil, rules, err, visitor.visit)
}
return visitor.rules, visitor.getError()
}

// RulesFor returns the list of rules that apply to a given user in a given namespace and error for all Resolvers. 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.
func (a *AggregateRuleResolver) RulesFor(user user.Info, namespace string) (rules []rbacv1.PolicyRule, retError error) {
visitor := &ruleAccumulator{}
a.VisitRulesFor(user, namespace, visitor.visit)
return visitor.rules, visitor.getError()
}

// VisitRulesFor invokes VisitRulesFor() on each resolver.
// If visitor() returns false, visiting is short-circuited for that resolver.
func (a *AggregateRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
for _, resolver := range a.resolvers {
resolver.VisitRulesFor(user, namespace, visitor)
}
}
39 changes: 0 additions & 39 deletions pkg/resolvers/clusterRoleTemplateBinding.go

This file was deleted.

59 changes: 59 additions & 0 deletions pkg/resolvers/crtbResolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package resolvers

import (
"fmt"

"github.com/rancher/webhook/pkg/auth"
v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/authentication/user"
)

// CRTBRuleResolver implements the rbacv1.AuthorizationRuleResolver interface.
type CRTBRuleResolver struct {
ClusterRoleTemplateBindings v3.ClusterRoleTemplateBindingCache
RoleTemplateResolver *auth.RoleTemplateResolver
}

// NewCRTBRuleResolver returns a new resolver for resolving rules given through ClusterRoleTemplateBindings.
func NewCRTBRuleResolver(crtbCache v3.ClusterRoleTemplateBindingCache, roleTemplateResolver *auth.RoleTemplateResolver) *CRTBRuleResolver {
return &CRTBRuleResolver{
ClusterRoleTemplateBindings: crtbCache,
RoleTemplateResolver: roleTemplateResolver,
}
}

// GetRoleReferenceRules is used to find which roles are granted by a rolebinding/clusterrolebinding. Since we don't
// use these primitives to refer to role templates, we don't have to implement this method.
func (c *CRTBRuleResolver) GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) {
return nil, ErrUnimplemented
}

// 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.
func (c *CRTBRuleResolver) RulesFor(user user.Info, namespace string) (rules []rbacv1.PolicyRule, retError error) {
visitor := &ruleAccumulator{}
c.VisitRulesFor(user, namespace, visitor.visit)
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.
func (c *CRTBRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
crtbs, err := c.ClusterRoleTemplateBindings.List(namespace, labels.Everything())
if err != nil {
visitor(nil, nil, err)
}

for _, crtb := range crtbs {
if crtb.UserName != user.GetName() {
continue
}
rtRules, err := c.RoleTemplateResolver.RulesFromTemplateName(crtb.RoleTemplateName)
if !visitRules(nil, rtRules, err, visitor) {
return
}
}
}
1 change: 0 additions & 1 deletion pkg/resolvers/projectRoleTemplateBinding.go

This file was deleted.

59 changes: 59 additions & 0 deletions pkg/resolvers/prtbResolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package resolvers

import (
"fmt"

"github.com/rancher/webhook/pkg/auth"
v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/authentication/user"
)

// PRTBRuleResolver implements the validation.AuthorizationRuleResolver interface.
type PRTBRuleResolver struct {
ProjectRoleTemplateBindings v3.ProjectRoleTemplateBindingCache
RoleTemplateResolver *auth.RoleTemplateResolver
}

// NewPRTBRuleResolver will create a new PRTBRuleResolver.
func NewPRTBRuleResolver(prtb v3.ProjectRoleTemplateBindingCache, roleTemplateResolver *auth.RoleTemplateResolver) *PRTBRuleResolver {
return &PRTBRuleResolver{
ProjectRoleTemplateBindings: prtb,
RoleTemplateResolver: roleTemplateResolver,
}
}

// GetRoleReferenceRules is used to find which roles are granted by a rolebinding/clusterrolebinding. Since we don't
// use these primitives to refer to role templates, we don't have to implement this method.
func (p *PRTBRuleResolver) GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) {
return nil, ErrUnimplemented
}

// 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.
func (p *PRTBRuleResolver) RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error) {
visitor := &ruleAccumulator{}
p.VisitRulesFor(user, namespace, visitor.visit)
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.
func (p *PRTBRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
prtbs, err := p.ProjectRoleTemplateBindings.List(namespace, labels.Everything())
if err != nil {
visitor(nil, nil, err)
}

for _, prtb := range prtbs {
if prtb.UserName != user.GetName() {
continue
}
rtRules, err := p.RoleTemplateResolver.RulesFromTemplateName(prtb.RoleTemplateName)
if !visitRules(nil, rtRules, err, visitor) {
return
}
}
}
3 changes: 3 additions & 0 deletions pkg/resolvers/resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package resolvers

// TODO scrap request for test cases
58 changes: 58 additions & 0 deletions pkg/resolvers/resolvers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Package resolvers resolves what rules different users and roleTemplates our bound to
package resolvers

import (
"fmt"

rbacv1 "k8s.io/api/rbac/v1"
)

// ErrUnimplemented is an error returned when a function is not implemented.
var ErrUnimplemented = fmt.Errorf("not implemented")

// ruleAccumulator based off kubernetes struct
// https://github.com/kubernetes/kubernetes/blob/d5fdf3135e7c99e5f81e67986ae930f6a2ffb047/pkg/registry/rbac/validation/rule.go#L124#L137
type ruleAccumulator struct {
rules []rbacv1.PolicyRule
errors []error
}

func (r *ruleAccumulator) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool {
if rule != nil {
r.rules = append(r.rules, *rule)
}
if err != nil {
r.errors = append(r.errors, err)
}
return true
}

// getError will combine all of the recorded errors into a single error.
func (r *ruleAccumulator) getError() error {
if len(r.errors) == 0 {
return nil
}
if len(r.errors) == 1 {
return r.errors[0]
}
var errorStr string
for _, err := range r.errors {
errorStr += fmt.Sprintf(", %s", err.Error())
}
const leadingChars = 2
return fmt.Errorf("[%s]", errorStr[leadingChars:])
}

// visitRules calls visitor on each rule in the list with the given Stringer and error.
func visitRules(source fmt.Stringer, rules []rbacv1.PolicyRule, err error, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool {
if err != nil && rules == nil {
return visitor(source, nil, err)
}
for i := range rules {
// we do not care about the return here
if !visitor(source, &rules[i], err) {
return false
}
}
return true
}
62 changes: 0 additions & 62 deletions pkg/resolvers/roleTemplate.go

This file was deleted.

29 changes: 6 additions & 23 deletions pkg/resources/validation/clusterroletemplatebinding/clusterrtb.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,32 @@ import (

apisv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
"github.com/rancher/webhook/pkg/auth"
v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3"
objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3"
"github.com/rancher/webhook/pkg/resolvers"
"github.com/rancher/webhook/pkg/resources/validation"
"github.com/rancher/wrangler/pkg/webhook"
"github.com/sirupsen/logrus"
admissionv1 "k8s.io/api/admission/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
k8validation "k8s.io/kubernetes/pkg/registry/rbac/validation"
"k8s.io/utils/trace"
)

var clusterRoleTemplateBindingGVR = schema.GroupVersionResource{
Group: "management.cattle.io",
Version: "v3",
Resource: "clusterroletemplatebindings",
}

// NewValidator will create a newly allocated Validator.
func NewValidator(defaultResolver k8validation.AuthorizationRuleResolver, roleTemplateResolver *auth.RoleTemplateResolver,
sar authorizationv1.SubjectAccessReviewInterface) *Validator {
func NewValidator(crtb v3.ClusterRoleTemplateBindingCache, defaultResolver k8validation.AuthorizationRuleResolver,
roleTemplateResolver *auth.RoleTemplateResolver) *Validator {
resolver := resolvers.NewAggregateRuleResolver(defaultResolver, resolvers.NewCRTBRuleResolver(crtb, roleTemplateResolver))
return &Validator{
resolver: defaultResolver,
resolver: resolver,
roleTemplateResolver: roleTemplateResolver,
sar: sar,
}
}

// Validator conforms to the webhook.Handler interface and is used for validating request for clusteroletemplatebindings.
type Validator struct {
resolver k8validation.AuthorizationRuleResolver
roleTemplateResolver *auth.RoleTemplateResolver
sar authorizationv1.SubjectAccessReviewInterface
}

// Admit is the entrypoint for the validator. Admit will return an error if it unable to process the request.
Expand Down Expand Up @@ -98,15 +90,6 @@ func (v *Validator) Admit(response *webhook.Response, request *webhook.Request)
return fmt.Errorf("failed to resolve rules from roletemplate '%s': %w", crtb.RoleTemplateName, err)
}

allowed, err := auth.EscalationAuthorized(request, clusterRoleTemplateBindingGVR, v.sar, crtb.ClusterName)
if err != nil {
logrus.Warnf("Failed to check for the 'escalate' verb on ClusterRoleTemplateBinding: %v", err)
}

if allowed {
response.Allowed = true
return nil
}
auth.SetEscalationResponse(response, auth.ConfirmNoEscalation(request, rules, crtb.ClusterName, v.resolver))

return nil
Expand Down
Loading

0 comments on commit f3573f6

Please sign in to comment.