From 0f0a5223dfc75337d03c9b80ae552ae8ef138eeb Mon Sep 17 00:00:00 2001 From: David Eads Date: Mon, 16 Oct 2017 13:56:13 -0400 Subject: [PATCH] rbac api changes for aggregation --- pkg/apis/rbac/types.go | 12 ++++++++ pkg/apis/rbac/validation/validation.go | 18 ++++++++++++ .../rbac/clusterrole/policybased/storage.go | 29 +++++++++++++++++-- staging/src/k8s.io/api/rbac/v1/types.go | 14 +++++++++ staging/src/k8s.io/api/rbac/v1alpha1/types.go | 14 +++++++++ staging/src/k8s.io/api/rbac/v1beta1/types.go | 13 +++++++++ 6 files changed, 97 insertions(+), 3 deletions(-) diff --git a/pkg/apis/rbac/types.go b/pkg/apis/rbac/types.go index 9ce2985f02..6fdd486d24 100644 --- a/pkg/apis/rbac/types.go +++ b/pkg/apis/rbac/types.go @@ -155,6 +155,18 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole Rules []PolicyRule + + // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. + // If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be + // stomped by the controller. + AggregationRule *AggregationRule +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + ClusterRoleSelectors []metav1.LabelSelector } // +genclient diff --git a/pkg/apis/rbac/validation/validation.go b/pkg/apis/rbac/validation/validation.go index 5b9de9d8cf..92fc5bdb15 100644 --- a/pkg/apis/rbac/validation/validation.go +++ b/pkg/apis/rbac/validation/validation.go @@ -18,6 +18,8 @@ package validation import ( "k8s.io/apimachinery/pkg/api/validation/path" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/rbac" @@ -61,6 +63,22 @@ func ValidateClusterRole(role *rbac.ClusterRole) field.ErrorList { allErrs = append(allErrs, err...) } } + + if role.AggregationRule != nil { + if len(role.AggregationRule.ClusterRoleSelectors) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil")) + } + for i, selector := range role.AggregationRule.ClusterRoleSelectors { + fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i) + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, fieldPath)...) + + selector, err := metav1.LabelSelectorAsSelector(&selector) + if err != nil { + allErrs = append(allErrs, field.Invalid(fieldPath, selector, "invalid label selector.")) + } + } + } + if len(allErrs) != 0 { return allErrs } diff --git a/pkg/registry/rbac/clusterrole/policybased/storage.go b/pkg/registry/rbac/clusterrole/policybased/storage.go index 5f7122fdee..cd53a9f470 100644 --- a/pkg/registry/rbac/clusterrole/policybased/storage.go +++ b/pkg/registry/rbac/clusterrole/policybased/storage.go @@ -18,7 +18,9 @@ limitations under the License. package policybased import ( - "k8s.io/apimachinery/pkg/api/errors" + "errors" + + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" @@ -40,6 +42,8 @@ func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.Auth return &Storage{s, ruleResolver} } +var fullAuthority = []rbac.PolicyRule{rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie()} + func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { return s.StandardStorage.Create(ctx, obj, createValidatingAdmission, includeUninitialized) @@ -48,8 +52,15 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, crea clusterRole := obj.(*rbac.ClusterRole) rules := clusterRole.Rules if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { - return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, err) } + // to set the aggregation rule, since it can gather anything, requires * on *.* + if hasAggregationRule(clusterRole) { + if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, fullAuthority); err != nil { + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, errors.New("must have cluster-admin privileges to use the aggregationRule")) + } + } + return s.StandardStorage.Create(ctx, obj, createValidatingAdmission, includeUninitialized) } @@ -60,6 +71,7 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { clusterRole := obj.(*rbac.ClusterRole) + oldClusterRole := oldObj.(*rbac.ClusterRole) // if we're only mutating fields needed for the GC to eventually delete this obj, return if rbacregistry.IsOnlyMutatingGCFields(obj, oldObj, kapihelper.Semantic) { @@ -68,10 +80,21 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up rules := clusterRole.Rules if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { - return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, err) } + // to change the aggregation rule, since it can gather anything and prevent tightening, requires * on *.* + if hasAggregationRule(clusterRole) || hasAggregationRule(oldClusterRole) { + if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, fullAuthority); err != nil { + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, errors.New("must have cluster-admin privileges to use the aggregationRule")) + } + } + return obj, nil }) return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation) } + +func hasAggregationRule(clusterRole *rbac.ClusterRole) bool { + return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 +} diff --git a/staging/src/k8s.io/api/rbac/v1/types.go b/staging/src/k8s.io/api/rbac/v1/types.go index 8dbd1a8b89..91990548bc 100644 --- a/staging/src/k8s.io/api/rbac/v1/types.go +++ b/staging/src/k8s.io/api/rbac/v1/types.go @@ -170,6 +170,20 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` + + // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. + // If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be + // stomped by the controller. + // +optional + AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"` +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + ClusterRoleSelectors []metav1.LabelSelector `json:"clusterRoleSelectors,omitempty" protobuf:"bytes,1,rep,name=clusterRoleSelectors"` } // +genclient diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/types.go b/staging/src/k8s.io/api/rbac/v1alpha1/types.go index 06fa6ce8e8..843d998ec9 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/types.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/types.go @@ -172,6 +172,20 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` + + // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. + // If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be + // stomped by the controller. + // +optional + AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"` +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + ClusterRoleSelectors []metav1.LabelSelector `json:"clusterRoleSelectors,omitempty" protobuf:"bytes,1,rep,name=clusterRoleSelectors"` } // +genclient diff --git a/staging/src/k8s.io/api/rbac/v1beta1/types.go b/staging/src/k8s.io/api/rbac/v1beta1/types.go index ee3964a3c0..091fc1dc95 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/types.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/types.go @@ -171,6 +171,19 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` + // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. + // If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be + // stomped by the controller. + // +optional + AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"` +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + ClusterRoleSelectors []metav1.LabelSelector `json:"clusterRoleSelectors,omitempty" protobuf:"bytes,1,rep,name=clusterRoleSelectors"` } // +genclient