From f34fb9b0ab37daf71d2d246db7e13a88a41ff359 Mon Sep 17 00:00:00 2001 From: David Eads Date: Fri, 20 Oct 2017 11:01:52 -0400 Subject: [PATCH] handle clusterrole migration --- .../rbac/clusterrole/policybased/storage.go | 5 +- pkg/registry/rbac/reconciliation/BUILD | 1 + .../reconciliation/clusterrole_interfaces.go | 8 + .../rbac/reconciliation/reconcile_role.go | 74 ++++- .../reconciliation/reconcile_role_test.go | 101 ++++++- .../rbac/reconciliation/role_interfaces.go | 7 + pkg/registry/rbac/rest/storage_rbac.go | 48 ++- .../rbac/bootstrappolicy/controller_policy.go | 8 + .../bootstrappolicy/controller_policy_test.go | 1 + .../authorizer/rbac/bootstrappolicy/policy.go | 36 ++- .../rbac/bootstrappolicy/policy_test.go | 11 +- .../testdata/cluster-roles.yaml | 275 ++++++++++-------- .../testdata/controller-role-bindings.yaml | 17 ++ .../testdata/controller-roles.yaml | 20 ++ 14 files changed, 480 insertions(+), 132 deletions(-) diff --git a/pkg/registry/rbac/clusterrole/policybased/storage.go b/pkg/registry/rbac/clusterrole/policybased/storage.go index cd53a9f470..0b32fdebf1 100644 --- a/pkg/registry/rbac/clusterrole/policybased/storage.go +++ b/pkg/registry/rbac/clusterrole/policybased/storage.go @@ -42,7 +42,10 @@ func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.Auth return &Storage{s, ruleResolver} } -var fullAuthority = []rbac.PolicyRule{rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie()} +var fullAuthority = []rbac.PolicyRule{ + rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(), + rbac.NewRule("*").URLs("*").RuleOrDie(), +} func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { diff --git a/pkg/registry/rbac/reconciliation/BUILD b/pkg/registry/rbac/reconciliation/BUILD index 332669868e..72d6ae1d5b 100644 --- a/pkg/registry/rbac/reconciliation/BUILD +++ b/pkg/registry/rbac/reconciliation/BUILD @@ -18,6 +18,7 @@ go_test( "//pkg/apis/core/helper:go_default_library", "//pkg/apis/rbac:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", ], ) diff --git a/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go b/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go index cd4f221f37..419cc1df26 100644 --- a/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go +++ b/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go @@ -66,6 +66,14 @@ func (o ClusterRoleRuleOwner) SetRules(in []rbac.PolicyRule) { o.ClusterRole.Rules = in } +func (o ClusterRoleRuleOwner) GetAggregationRule() *rbac.AggregationRule { + return o.ClusterRole.AggregationRule +} + +func (o ClusterRoleRuleOwner) SetAggregationRule(in *rbac.AggregationRule) { + o.ClusterRole.AggregationRule = in +} + type ClusterRoleModifier struct { Client internalversion.ClusterRoleInterface } diff --git a/pkg/registry/rbac/reconciliation/reconcile_role.go b/pkg/registry/rbac/reconciliation/reconcile_role.go index 873b329b0a..b460599101 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_role.go +++ b/pkg/registry/rbac/reconciliation/reconcile_role.go @@ -20,7 +20,9 @@ import ( "fmt" "reflect" + "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -51,6 +53,8 @@ type RuleOwner interface { SetAnnotations(map[string]string) GetRules() []rbac.PolicyRule SetRules([]rbac.PolicyRule) + GetAggregationRule() *rbac.AggregationRule + SetAggregationRule(*rbac.AggregationRule) DeepCopyRuleOwner() RuleOwner } @@ -75,6 +79,11 @@ type ReconcileClusterRoleResult struct { // ExtraRules contains extra permissions the currently persisted role had ExtraRules []rbac.PolicyRule + // MissingAggregationRuleSelectors contains expected selectors that were missing from the currently persisted role + MissingAggregationRuleSelectors []metav1.LabelSelector + // ExtraAggregationRuleSelectors contains extra selectors the currently persisted role had + ExtraAggregationRuleSelectors []metav1.LabelSelector + // Operation is the API operation required to reconcile. // If no reconciliation was needed, it is set to ReconcileNone. // If options.Confirm == false, the reconcile was in dry-run mode, so the operation was not performed. @@ -101,10 +110,15 @@ func (o *ReconcileRoleOptions) run(attempts int) (*ReconcileClusterRoleResult, e existing, err := o.Client.Get(o.Role.GetNamespace(), o.Role.GetName()) switch { case errors.IsNotFound(err): + aggregationRule := o.Role.GetAggregationRule() + if aggregationRule == nil { + aggregationRule = &rbac.AggregationRule{} + } result = &ReconcileClusterRoleResult{ - Role: o.Role, - MissingRules: o.Role.GetRules(), - Operation: ReconcileCreate, + Role: o.Role, + MissingRules: o.Role.GetRules(), + MissingAggregationRuleSelectors: aggregationRule.ClusterRoleSelectors, + Operation: ReconcileCreate, } case err != nil: @@ -195,6 +209,26 @@ func computeReconciledRole(existing, expected RuleOwner, removeExtraPermissions result.Operation = ReconcileUpdate } + // Compute extra and missing rules + _, result.ExtraAggregationRuleSelectors = aggregationRuleCovers(expected.GetAggregationRule(), existing.GetAggregationRule()) + _, result.MissingAggregationRuleSelectors = aggregationRuleCovers(existing.GetAggregationRule(), expected.GetAggregationRule()) + + switch { + case !removeExtraPermissions && len(result.MissingAggregationRuleSelectors) > 0: + // add missing rules in the union case + aggregationRule := result.Role.GetAggregationRule() + if aggregationRule == nil { + aggregationRule = &rbac.AggregationRule{} + } + aggregationRule.ClusterRoleSelectors = append(aggregationRule.ClusterRoleSelectors, result.MissingAggregationRuleSelectors...) + result.Role.SetAggregationRule(aggregationRule) + result.Operation = ReconcileUpdate + + case removeExtraPermissions && (len(result.MissingAggregationRuleSelectors) > 0 || len(result.ExtraAggregationRuleSelectors) > 0): + result.Role.SetAggregationRule(expected.GetAggregationRule()) + result.Operation = ReconcileUpdate + } + return result, nil } @@ -211,3 +245,37 @@ func merge(maps ...map[string]string) map[string]string { } return output } + +// aggregationRuleCovers determines whether or not the ownerSelectors cover the servantSelectors in terms of semantically +// equal label selectors. +// It returns whether or not the ownerSelectors cover and a list of the rules that the ownerSelectors do not cover. +func aggregationRuleCovers(ownerRule, servantRule *rbac.AggregationRule) (bool, []metav1.LabelSelector) { + switch { + case ownerRule == nil && servantRule == nil: + return true, []metav1.LabelSelector{} + case ownerRule == nil && servantRule != nil: + return false, servantRule.ClusterRoleSelectors + case ownerRule != nil && servantRule == nil: + return true, []metav1.LabelSelector{} + + } + + ownerSelectors := ownerRule.ClusterRoleSelectors + servantSelectors := servantRule.ClusterRoleSelectors + uncoveredSelectors := []metav1.LabelSelector{} + + for _, servantSelector := range servantSelectors { + covered := false + for _, ownerSelector := range ownerSelectors { + if equality.Semantic.DeepEqual(ownerSelector, servantSelector) { + covered = true + break + } + } + if !covered { + uncoveredSelectors = append(uncoveredSelectors, servantSelector) + } + } + + return (len(uncoveredSelectors) == 0), uncoveredSelectors +} diff --git a/pkg/registry/rbac/reconciliation/reconcile_role_test.go b/pkg/registry/rbac/reconciliation/reconcile_role_test.go index 19ada92949..1d30c9cad6 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_role_test.go +++ b/pkg/registry/rbac/reconciliation/reconcile_role_test.go @@ -20,12 +20,16 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/diff" "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/rbac" ) func role(rules []rbac.PolicyRule, labels map[string]string, annotations map[string]string) *rbac.ClusterRole { - return &rbac.ClusterRole{Rules: rules, ObjectMeta: metav1.ObjectMeta{Labels: labels, Annotations: annotations}} + return &rbac.ClusterRole{ + Rules: rules, + ObjectMeta: metav1.ObjectMeta{Labels: labels, Annotations: annotations}, + } } func rules(resources ...string) []rbac.PolicyRule { @@ -38,7 +42,7 @@ func rules(resources ...string) []rbac.PolicyRule { type ss map[string]string -func TestComputeReconciledRole(t *testing.T) { +func TestComputeReconciledRoleRules(t *testing.T) { tests := map[string]struct { expectedRole *rbac.ClusterRole actualRole *rbac.ClusterRole @@ -273,3 +277,96 @@ func TestComputeReconciledRole(t *testing.T) { } } } + +func aggregatedRole(aggregationRule *rbac.AggregationRule) *rbac.ClusterRole { + return &rbac.ClusterRole{ + AggregationRule: aggregationRule, + } +} + +func aggregationrule(selectors []map[string]string) *rbac.AggregationRule { + ret := &rbac.AggregationRule{} + for _, selector := range selectors { + ret.ClusterRoleSelectors = append(ret.ClusterRoleSelectors, + metav1.LabelSelector{MatchLabels: selector}) + } + return ret +} + +func TestComputeReconciledRoleAggregationRules(t *testing.T) { + tests := map[string]struct { + expectedRole *rbac.ClusterRole + actualRole *rbac.ClusterRole + removeExtraPermissions bool + + expectedReconciledRole *rbac.ClusterRole + expectedReconciliationNeeded bool + }{ + "empty": { + expectedRole: aggregatedRole(&rbac.AggregationRule{}), + actualRole: aggregatedRole(nil), + removeExtraPermissions: true, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "empty-2": { + expectedRole: aggregatedRole(&rbac.AggregationRule{}), + actualRole: aggregatedRole(&rbac.AggregationRule{}), + removeExtraPermissions: true, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "match without union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + removeExtraPermissions: true, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "match with union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + removeExtraPermissions: false, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "different rules without union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}})), + removeExtraPermissions: true, + + expectedReconciledRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + expectedReconciliationNeeded: true, + }, + "different rules with union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}})), + removeExtraPermissions: false, + + expectedReconciledRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}, {"foo": "bar"}})), + expectedReconciliationNeeded: true, + }, + } + + for k, tc := range tests { + actualRole := ClusterRoleRuleOwner{ClusterRole: tc.actualRole} + expectedRole := ClusterRoleRuleOwner{ClusterRole: tc.expectedRole} + result, err := computeReconciledRole(actualRole, expectedRole, tc.removeExtraPermissions) + if err != nil { + t.Errorf("%s: %v", k, err) + continue + } + reconciliationNeeded := result.Operation != ReconcileNone + if reconciliationNeeded != tc.expectedReconciliationNeeded { + t.Errorf("%s: Expected\n\t%v\ngot\n\t%v", k, tc.expectedReconciliationNeeded, reconciliationNeeded) + continue + } + if reconciliationNeeded && !helper.Semantic.DeepEqual(result.Role.(ClusterRoleRuleOwner).ClusterRole, tc.expectedReconciledRole) { + t.Errorf("%s: %v", k, diff.ObjectDiff(tc.expectedReconciledRole, result.Role.(ClusterRoleRuleOwner).ClusterRole)) + } + } +} diff --git a/pkg/registry/rbac/reconciliation/role_interfaces.go b/pkg/registry/rbac/reconciliation/role_interfaces.go index 113b33fcab..b46e9e872e 100644 --- a/pkg/registry/rbac/reconciliation/role_interfaces.go +++ b/pkg/registry/rbac/reconciliation/role_interfaces.go @@ -69,6 +69,13 @@ func (o RoleRuleOwner) SetRules(in []rbac.PolicyRule) { o.Role.Rules = in } +func (o RoleRuleOwner) GetAggregationRule() *rbac.AggregationRule { + return nil +} + +func (o RoleRuleOwner) SetAggregationRule(in *rbac.AggregationRule) { +} + type RoleModifier struct { Client internalversion.RolesGetter NamespaceClient core.NamespaceInterface diff --git a/pkg/registry/rbac/rest/storage_rbac.go b/pkg/registry/rbac/rest/storage_rbac.go index e1152a8fe8..88fdf73d59 100644 --- a/pkg/registry/rbac/rest/storage_rbac.go +++ b/pkg/registry/rbac/rest/storage_rbac.go @@ -26,6 +26,7 @@ import ( rbacapiv1 "k8s.io/api/rbac/v1" rbacapiv1alpha1 "k8s.io/api/rbac/v1alpha1" rbacapiv1beta1 "k8s.io/api/rbac/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -134,10 +135,11 @@ func (p RESTStorageProvider) storage(version schema.GroupVersion, apiResourceCon func (p RESTStorageProvider) PostStartHook() (string, genericapiserver.PostStartHookFunc, error) { policy := &PolicyData{ - ClusterRoles: append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...), - ClusterRoleBindings: append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...), - Roles: bootstrappolicy.NamespaceRoles(), - RoleBindings: bootstrappolicy.NamespaceRoleBindings(), + ClusterRoles: append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...), + ClusterRoleBindings: append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...), + Roles: bootstrappolicy.NamespaceRoles(), + RoleBindings: bootstrappolicy.NamespaceRoleBindings(), + ClusterRolesToAggregate: bootstrappolicy.ClusterRolesToAggregate(), } return PostStartHookName, policy.EnsureRBACPolicy(), nil } @@ -147,6 +149,8 @@ type PolicyData struct { ClusterRoleBindings []rbac.ClusterRoleBinding Roles map[string][]rbac.Role RoleBindings map[string][]rbac.RoleBinding + // ClusterRolesToAggregate maps from previous clusterrole name to the new clusterrole name + ClusterRolesToAggregate map[string]string } func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { @@ -176,6 +180,13 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { return false, nil } + // if the new cluster roles to aggregate do not yet exist, then we need to copy the old roles if they don't exist + // in new locations + if err := primeAggregatedClusterRoles(p.ClusterRolesToAggregate, clientset); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to prime aggregated clusterroles: %v", err)) + return false, nil + } + // ensure bootstrap roles are created or reconciled for _, clusterRole := range p.ClusterRoles { opts := reconciliation.ReconcileRoleOptions{ @@ -310,3 +321,32 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { func (p RESTStorageProvider) GroupName() string { return rbac.GroupName } + +// primeAggregatedClusterRoles copies roles that have transitioned to aggregated roles and may need to pick up changes +// that were done to the legacy roles. +func primeAggregatedClusterRoles(clusterRolesToAggregate map[string]string, clusterRoleClient rbacclient.ClusterRolesGetter) error { + for oldName, newName := range clusterRolesToAggregate { + _, err := clusterRoleClient.ClusterRoles().Get(newName, metav1.GetOptions{}) + if err == nil { + continue + } + if !apierrors.IsNotFound(err) { + return err + } + + existingRole, err := clusterRoleClient.ClusterRoles().Get(oldName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + continue + } + if err != nil { + return err + } + glog.V(1).Infof("migrating %v to %v", existingRole.Name, newName) + existingRole.Name = newName + if _, err := clusterRoleClient.ClusterRoles().Create(existingRole); err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + } + + return nil +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go index 6a32aa80a2..f1270f611b 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go @@ -68,6 +68,14 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) { eventsRule(), }, }) + addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "clusterrole-aggregation-controller"}, + Rules: []rbac.PolicyRule{ + // this controller must have full permissions to allow it to mutate any role in any way + rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(), + rbac.NewRule("*").URLs("*").RuleOrDie(), + }, + }) addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "cronjob-controller"}, Rules: []rbac.PolicyRule{ diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go index e1c98019bc..f1064f2708 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go @@ -32,6 +32,7 @@ var rolesWithAllowStar = sets.NewString( saRolePrefix+"generic-garbage-collector", saRolePrefix+"resourcequota-controller", saRolePrefix+"horizontal-pod-autoscaler", + saRolePrefix+"clusterrole-aggregation-controller", ) // TestNoStarsForControllers confirms that no controller role has star verbs, groups, diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go index 01d5547fbd..16a5d9a44e 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go @@ -176,6 +176,30 @@ func ClusterRoles() []rbac.ClusterRole { { // a role for a namespace level admin. It is `edit` plus the power to grant permissions to other users. ObjectMeta: metav1.ObjectMeta{Name: "admin"}, + AggregationRule: &rbac.AggregationRule{ + ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-admin": "true"}}}, + }, + }, + { + // a role for a namespace level editor. It grants access to all user level actions in a namespace. + // It does not grant powers for "privileged" resources which are domain of the system: `/status` + // subresources or `quota`/`limits` which are used to control namespaces + ObjectMeta: metav1.ObjectMeta{Name: "edit"}, + AggregationRule: &rbac.AggregationRule{ + ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-edit": "true"}}}, + }, + }, + { + // a role for namespace level viewing. It grants Read-only access to non-escalating resources in + // a namespace. + ObjectMeta: metav1.ObjectMeta{Name: "view"}, + AggregationRule: &rbac.AggregationRule{ + ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-view": "true"}}}, + }, + }, + { + // a role for a namespace level admin. It is `edit` plus the power to grant permissions to other users. + ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-admin", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-admin": "true"}}, Rules: []rbac.PolicyRule{ rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts", @@ -211,7 +235,7 @@ func ClusterRoles() []rbac.ClusterRole { // a role for a namespace level editor. It grants access to all user level actions in a namespace. // It does not grant powers for "privileged" resources which are domain of the system: `/status` // subresources or `quota`/`limits` which are used to control namespaces - ObjectMeta: metav1.ObjectMeta{Name: "edit"}, + ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-edit", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-edit": "true"}}, Rules: []rbac.PolicyRule{ rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts", @@ -242,7 +266,7 @@ func ClusterRoles() []rbac.ClusterRole { { // a role for namespace level viewing. It grants Read-only access to non-escalating resources in // a namespace. - ObjectMeta: metav1.ObjectMeta{Name: "view"}, + ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-view", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-view": "true"}}, Rules: []rbac.PolicyRule{ rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods", "replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts", "services", "endpoints", "persistentvolumeclaims", "configmaps").RuleOrDie(), @@ -444,3 +468,11 @@ func ClusterRoleBindings() []rbac.ClusterRoleBinding { return rolebindings } + +func ClusterRolesToAggregate() map[string]string { + return map[string]string{ + "admin": "system:aggregate-to-admin", + "edit": "system:aggregate-to-edit", + "view": "system:aggregate-to-view", + } +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go index 41a8d92017..830d92576f 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go @@ -53,11 +53,11 @@ func getSemanticRoles(roles []rbac.ClusterRole) semanticRoles { for i := range roles { role := roles[i] switch role.Name { - case "admin": + case "system:aggregate-to-admin": ret.admin = &role - case "edit": + case "system:aggregate-to-edit": ret.edit = &role - case "view": + case "system:aggregate-to-view": ret.view = &role } } @@ -319,8 +319,9 @@ func TestClusterRoleLabel(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if got, want := accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}; !reflect.DeepEqual(got, want) { - t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), got, want) + + if accessor.GetLabels()["kubernetes.io/bootstrapping"] != "rbac-defaults" { + t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}) } } diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml index bee0f1354e..7fe801931a 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml @@ -1,6 +1,10 @@ apiVersion: v1 items: -- apiVersion: rbac.authorization.k8s.io/v1 +- aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: @@ -9,6 +13,51 @@ items: labels: kubernetes.io/bootstrapping: rbac-defaults name: admin + rules: null +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: cluster-admin + rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + - nonResourceURLs: + - '*' + verbs: + - '*' +- aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.k8s.io/aggregate-to-edit: "true" + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: edit + rules: null +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + rbac.authorization.k8s.io/aggregate-to-admin: "true" + name: system:aggregate-to-admin rules: - apiGroups: - "" @@ -185,27 +234,8 @@ items: creationTimestamp: null labels: kubernetes.io/bootstrapping: rbac-defaults - name: cluster-admin - rules: - - apiGroups: - - '*' - resources: - - '*' - verbs: - - '*' - - nonResourceURLs: - - '*' - verbs: - - '*' -- apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - annotations: - rbac.authorization.kubernetes.io/autoupdate: "true" - creationTimestamp: null - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: edit + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: system:aggregate-to-edit rules: - apiGroups: - "" @@ -354,6 +384,108 @@ items: - patch - update - watch +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + rbac.authorization.k8s.io/aggregate-to-view: "true" + name: system:aggregate-to-view + rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - persistentvolumeclaims + - pods + - replicationcontrollers + - replicationcontrollers/scale + - serviceaccounts + - services + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - bindings + - events + - limitranges + - namespaces/status + - pods/log + - pods/status + - replicationcontrollers/status + - resourcequotas + - resourcequotas/status + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch + - apiGroups: + - apps + resources: + - daemonsets + - deployments + - deployments/scale + - replicasets + - replicasets/scale + - statefulsets + verbs: + - get + - list + - watch + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - apiGroups: + - extensions + resources: + - daemonsets + - deployments + - deployments/scale + - ingresses + - replicasets + - replicasets/scale + - replicationcontrollers/scale + verbs: + - get + - list + - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -935,7 +1067,11 @@ items: - create - patch - update -- apiVersion: rbac.authorization.k8s.io/v1 +- aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: @@ -944,97 +1080,6 @@ items: labels: kubernetes.io/bootstrapping: rbac-defaults name: view - rules: - - apiGroups: - - "" - resources: - - configmaps - - endpoints - - persistentvolumeclaims - - pods - - replicationcontrollers - - replicationcontrollers/scale - - serviceaccounts - - services - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - bindings - - events - - limitranges - - namespaces/status - - pods/log - - pods/status - - replicationcontrollers/status - - resourcequotas - - resourcequotas/status - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - namespaces - verbs: - - get - - list - - watch - - apiGroups: - - apps - resources: - - daemonsets - - deployments - - deployments/scale - - replicasets - - replicasets/scale - - statefulsets - verbs: - - get - - list - - watch - - apiGroups: - - autoscaling - resources: - - horizontalpodautoscalers - verbs: - - get - - list - - watch - - apiGroups: - - batch - resources: - - cronjobs - - jobs - verbs: - - get - - list - - watch - - apiGroups: - - extensions - resources: - - daemonsets - - deployments - - deployments/scale - - ingresses - - replicasets - - replicasets/scale - - replicationcontrollers/scale - verbs: - - get - - list - - watch - - apiGroups: - - policy - resources: - - poddisruptionbudgets - verbs: - - get - - list - - watch + rules: null kind: List metadata: {} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml index ddb59dbcef..ff5d47867b 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml @@ -34,6 +34,23 @@ items: - kind: ServiceAccount name: certificate-controller namespace: kube-system +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:clusterrole-aggregation-controller + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:controller:clusterrole-aggregation-controller + subjects: + - kind: ServiceAccount + name: clusterrole-aggregation-controller + namespace: kube-system - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml index d01adab43c..0af7efbfa3 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml @@ -87,6 +87,26 @@ items: - create - patch - update +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:clusterrole-aggregation-controller + rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + - nonResourceURLs: + - '*' + verbs: + - '*' - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: