diff --git a/pkg/apis/rbac/helpers.go b/pkg/apis/rbac/helpers.go index c0bef888fd7..8b4677bb869 100644 --- a/pkg/apis/rbac/helpers.go +++ b/pkg/apis/rbac/helpers.go @@ -282,6 +282,22 @@ func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder { } } +func NewRoleBindingForClusterRole(roleName, namespace string) *RoleBindingBuilder { + return &RoleBindingBuilder{ + RoleBinding: RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: namespace, + }, + RoleRef: RoleRef{ + APIGroup: GroupName, + Kind: "ClusterRole", + Name: roleName, + }, + }, + } +} + // Groups adds the specified groups as the subjects of the RoleBinding. func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder { for _, group := range groups { diff --git a/pkg/registry/rbac/reconciliation/BUILD b/pkg/registry/rbac/reconciliation/BUILD index 79a1484a3d1..50c6dab9e05 100644 --- a/pkg/registry/rbac/reconciliation/BUILD +++ b/pkg/registry/rbac/reconciliation/BUILD @@ -11,8 +11,8 @@ load( go_test( name = "go_default_test", srcs = [ - "reconcile_clusterrolebindings_test.go", "reconcile_role_test.go", + "reconcile_rolebindings_test.go", ], library = ":go_default_library", tags = ["automanaged"], @@ -26,9 +26,12 @@ go_test( go_library( name = "go_default_library", srcs = [ - "reconcile_clusterrolebindings.go", + "clusterrole_interfaces.go", + "clusterrolebinding_interfaces.go", "reconcile_role.go", + "reconcile_rolebindings.go", "role_interfaces.go", + "rolebinding_interfaces.go", ], tags = ["automanaged"], deps = [ @@ -38,6 +41,7 @@ go_library( "//pkg/registry/rbac/validation:go_default_library", "//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", + "//vendor:k8s.io/apimachinery/pkg/types", ], ) diff --git a/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go b/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go new file mode 100644 index 00000000000..93928b20c8e --- /dev/null +++ b/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go @@ -0,0 +1,88 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package reconciliation + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" +) + +type ClusterRoleRuleOwner struct { + ClusterRole *rbac.ClusterRole +} + +func (o ClusterRoleRuleOwner) GetNamespace() string { + return o.ClusterRole.Namespace +} + +func (o ClusterRoleRuleOwner) GetName() string { + return o.ClusterRole.Name +} + +func (o ClusterRoleRuleOwner) GetLabels() map[string]string { + return o.ClusterRole.Labels +} + +func (o ClusterRoleRuleOwner) SetLabels(in map[string]string) { + o.ClusterRole.Labels = in +} + +func (o ClusterRoleRuleOwner) GetAnnotations() map[string]string { + return o.ClusterRole.Annotations +} + +func (o ClusterRoleRuleOwner) SetAnnotations(in map[string]string) { + o.ClusterRole.Annotations = in +} + +func (o ClusterRoleRuleOwner) GetRules() []rbac.PolicyRule { + return o.ClusterRole.Rules +} + +func (o ClusterRoleRuleOwner) SetRules(in []rbac.PolicyRule) { + o.ClusterRole.Rules = in +} + +type ClusterRoleModifier struct { + Client internalversion.ClusterRoleInterface +} + +func (c ClusterRoleModifier) Get(namespace, name string) (RuleOwner, error) { + ret, err := c.Client.Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return ClusterRoleRuleOwner{ClusterRole: ret}, err +} + +func (c ClusterRoleModifier) Create(in RuleOwner) (RuleOwner, error) { + ret, err := c.Client.Create(in.(ClusterRoleRuleOwner).ClusterRole) + if err != nil { + return nil, err + } + return ClusterRoleRuleOwner{ClusterRole: ret}, err +} + +func (c ClusterRoleModifier) Update(in RuleOwner) (RuleOwner, error) { + ret, err := c.Client.Update(in.(ClusterRoleRuleOwner).ClusterRole) + if err != nil { + return nil, err + } + return ClusterRoleRuleOwner{ClusterRole: ret}, err + +} diff --git a/pkg/registry/rbac/reconciliation/clusterrolebinding_interfaces.go b/pkg/registry/rbac/reconciliation/clusterrolebinding_interfaces.go new file mode 100644 index 00000000000..79c392d6e91 --- /dev/null +++ b/pkg/registry/rbac/reconciliation/clusterrolebinding_interfaces.go @@ -0,0 +1,101 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package reconciliation + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" +) + +type ClusterRoleBindingAdapter struct { + ClusterRoleBinding *rbac.ClusterRoleBinding +} + +func (o ClusterRoleBindingAdapter) GetNamespace() string { + return o.ClusterRoleBinding.Namespace +} + +func (o ClusterRoleBindingAdapter) GetName() string { + return o.ClusterRoleBinding.Name +} + +func (o ClusterRoleBindingAdapter) GetUID() types.UID { + return o.ClusterRoleBinding.UID +} + +func (o ClusterRoleBindingAdapter) GetLabels() map[string]string { + return o.ClusterRoleBinding.Labels +} + +func (o ClusterRoleBindingAdapter) SetLabels(in map[string]string) { + o.ClusterRoleBinding.Labels = in +} + +func (o ClusterRoleBindingAdapter) GetAnnotations() map[string]string { + return o.ClusterRoleBinding.Annotations +} + +func (o ClusterRoleBindingAdapter) SetAnnotations(in map[string]string) { + o.ClusterRoleBinding.Annotations = in +} + +func (o ClusterRoleBindingAdapter) GetRoleRef() rbac.RoleRef { + return o.ClusterRoleBinding.RoleRef +} + +func (o ClusterRoleBindingAdapter) GetSubjects() []rbac.Subject { + return o.ClusterRoleBinding.Subjects +} + +func (o ClusterRoleBindingAdapter) SetSubjects(in []rbac.Subject) { + o.ClusterRoleBinding.Subjects = in +} + +type ClusterRoleBindingClientAdapter struct { + Client internalversion.ClusterRoleBindingInterface +} + +func (c ClusterRoleBindingClientAdapter) Get(namespace, name string) (RoleBinding, error) { + ret, err := c.Client.Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return ClusterRoleBindingAdapter{ClusterRoleBinding: ret}, err +} + +func (c ClusterRoleBindingClientAdapter) Create(in RoleBinding) (RoleBinding, error) { + ret, err := c.Client.Create(in.(ClusterRoleBindingAdapter).ClusterRoleBinding) + if err != nil { + return nil, err + } + return ClusterRoleBindingAdapter{ClusterRoleBinding: ret}, err +} + +func (c ClusterRoleBindingClientAdapter) Update(in RoleBinding) (RoleBinding, error) { + ret, err := c.Client.Update(in.(ClusterRoleBindingAdapter).ClusterRoleBinding) + if err != nil { + return nil, err + } + return ClusterRoleBindingAdapter{ClusterRoleBinding: ret}, err + +} + +func (c ClusterRoleBindingClientAdapter) Delete(namespace, name string, uid types.UID) error { + return c.Client.Delete(name, &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}}) +} diff --git a/pkg/registry/rbac/reconciliation/reconcile_role.go b/pkg/registry/rbac/reconciliation/reconcile_role.go index 03641c32db2..c29f105a14d 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_role.go +++ b/pkg/registry/rbac/reconciliation/reconcile_role.go @@ -21,10 +21,8 @@ import ( "reflect" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/rbac" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" "k8s.io/kubernetes/pkg/registry/rbac/validation" ) @@ -54,7 +52,7 @@ type RuleOwner interface { SetRules([]rbac.PolicyRule) } -type ReconcileClusterRoleOptions struct { +type ReconcileRoleOptions struct { // Role is the expected role that will be reconciled Role RuleOwner // Confirm indicates writes should be performed. When false, results are returned as a dry-run. @@ -85,11 +83,11 @@ type ReconcileClusterRoleResult struct { Protected bool } -func (o *ReconcileClusterRoleOptions) Run() (*ReconcileClusterRoleResult, error) { +func (o *ReconcileRoleOptions) Run() (*ReconcileClusterRoleResult, error) { return o.run(0) } -func (o *ReconcileClusterRoleOptions) run(attempts int) (*ReconcileClusterRoleResult, error) { +func (o *ReconcileRoleOptions) run(attempts int) (*ReconcileClusterRoleResult, error) { // This keeps us from retrying forever if a role keeps appearing and disappearing as we reconcile. // Conflict errors on update are handled at a higher level. if attempts > 2 { @@ -215,68 +213,3 @@ func merge(maps ...map[string]string) map[string]string { } return output } - -type ClusterRoleRuleOwner struct { - ClusterRole *rbac.ClusterRole -} - -func (o ClusterRoleRuleOwner) GetNamespace() string { - return o.ClusterRole.Namespace -} - -func (o ClusterRoleRuleOwner) GetName() string { - return o.ClusterRole.Name -} - -func (o ClusterRoleRuleOwner) GetLabels() map[string]string { - return o.ClusterRole.Labels -} - -func (o ClusterRoleRuleOwner) SetLabels(in map[string]string) { - o.ClusterRole.Labels = in -} - -func (o ClusterRoleRuleOwner) GetAnnotations() map[string]string { - return o.ClusterRole.Annotations -} - -func (o ClusterRoleRuleOwner) SetAnnotations(in map[string]string) { - o.ClusterRole.Annotations = in -} - -func (o ClusterRoleRuleOwner) GetRules() []rbac.PolicyRule { - return o.ClusterRole.Rules -} - -func (o ClusterRoleRuleOwner) SetRules(in []rbac.PolicyRule) { - o.ClusterRole.Rules = in -} - -type ClusterRoleModifier struct { - Client internalversion.ClusterRoleInterface -} - -func (c ClusterRoleModifier) Get(namespace, name string) (RuleOwner, error) { - ret, err := c.Client.Get(name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return ClusterRoleRuleOwner{ClusterRole: ret}, err -} - -func (c ClusterRoleModifier) Create(in RuleOwner) (RuleOwner, error) { - ret, err := c.Client.Create(in.(ClusterRoleRuleOwner).ClusterRole) - if err != nil { - return nil, err - } - return ClusterRoleRuleOwner{ClusterRole: ret}, err -} - -func (c ClusterRoleModifier) Update(in RuleOwner) (RuleOwner, error) { - ret, err := c.Client.Create(in.(ClusterRoleRuleOwner).ClusterRole) - if err != nil { - return nil, err - } - return ClusterRoleRuleOwner{ClusterRole: ret}, err - -} diff --git a/pkg/registry/rbac/reconciliation/reconcile_clusterrolebindings.go b/pkg/registry/rbac/reconciliation/reconcile_rolebindings.go similarity index 74% rename from pkg/registry/rbac/reconciliation/reconcile_clusterrolebindings.go rename to pkg/registry/rbac/reconciliation/reconcile_rolebindings.go index fb4d406ecc5..fcb6eac61d9 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_clusterrolebindings.go +++ b/pkg/registry/rbac/reconciliation/reconcile_rolebindings.go @@ -21,29 +21,48 @@ import ( "reflect" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/rbac" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" ) -// ReconcileClusterRoleBindingOptions holds options for running a role binding reconciliation -type ReconcileClusterRoleBindingOptions struct { +type RoleBindingModifier interface { + Get(namespace, name string) (RoleBinding, error) + Delete(namespace, name string, uid types.UID) error + Create(RoleBinding) (RoleBinding, error) + Update(RoleBinding) (RoleBinding, error) +} + +type RoleBinding interface { + GetNamespace() string + GetName() string + GetUID() types.UID + GetLabels() map[string]string + SetLabels(map[string]string) + GetAnnotations() map[string]string + SetAnnotations(map[string]string) + GetRoleRef() rbac.RoleRef + GetSubjects() []rbac.Subject + SetSubjects([]rbac.Subject) +} + +// ReconcileRoleBindingOptions holds options for running a role binding reconciliation +type ReconcileRoleBindingOptions struct { // RoleBinding is the expected rolebinding that will be reconciled - RoleBinding *rbac.ClusterRoleBinding + RoleBinding RoleBinding // Confirm indicates writes should be performed. When false, results are returned as a dry-run. Confirm bool // RemoveExtraSubjects indicates reconciliation should remove extra subjects from an existing role binding RemoveExtraSubjects bool // Client is used to look up existing rolebindings, and create/update the rolebinding when Confirm=true - Client internalversion.ClusterRoleBindingInterface + Client RoleBindingModifier } // ReconcileClusterRoleBindingResult holds the result of a reconciliation operation. type ReconcileClusterRoleBindingResult struct { // RoleBinding is the reconciled rolebinding from the reconciliation operation. // If the reconcile was performed as a dry-run, or the existing rolebinding was protected, the reconciled rolebinding is not persisted. - RoleBinding *rbac.ClusterRoleBinding + RoleBinding RoleBinding // MissingSubjects contains expected subjects that were missing from the currently persisted rolebinding MissingSubjects []rbac.Subject @@ -60,11 +79,11 @@ type ReconcileClusterRoleBindingResult struct { Protected bool } -func (o *ReconcileClusterRoleBindingOptions) Run() (*ReconcileClusterRoleBindingResult, error) { +func (o *ReconcileRoleBindingOptions) Run() (*ReconcileClusterRoleBindingResult, error) { return o.run(0) } -func (o *ReconcileClusterRoleBindingOptions) run(attempts int) (*ReconcileClusterRoleBindingResult, error) { +func (o *ReconcileRoleBindingOptions) run(attempts int) (*ReconcileClusterRoleBindingResult, error) { // This keeps us from retrying forever if a rolebinding keeps appearing and disappearing as we reconcile. // Conflict errors on update are handled at a higher level. if attempts > 3 { @@ -73,12 +92,12 @@ func (o *ReconcileClusterRoleBindingOptions) run(attempts int) (*ReconcileCluste var result *ReconcileClusterRoleBindingResult - existingBinding, err := o.Client.Get(o.RoleBinding.Name, metav1.GetOptions{}) + existingBinding, err := o.Client.Get(o.RoleBinding.GetNamespace(), o.RoleBinding.GetName()) switch { case errors.IsNotFound(err): result = &ReconcileClusterRoleBindingResult{ RoleBinding: o.RoleBinding, - MissingSubjects: o.RoleBinding.Subjects, + MissingSubjects: o.RoleBinding.GetSubjects(), Operation: ReconcileCreate, } @@ -104,10 +123,7 @@ func (o *ReconcileClusterRoleBindingOptions) run(attempts int) (*ReconcileCluste switch result.Operation { case ReconcileRecreate: // Try deleting - err := o.Client.Delete( - existingBinding.Name, - &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &existingBinding.UID}}, - ) + err := o.Client.Delete(existingBinding.GetNamespace(), existingBinding.GetName(), existingBinding.GetUID()) switch { case err == nil, errors.IsNotFound(err): // object no longer exists, as desired @@ -155,13 +171,13 @@ func (o *ReconcileClusterRoleBindingOptions) run(attempts int) (*ReconcileCluste // computeReconciledRoleBinding returns the rolebinding that must be created and/or updated to make the // existing rolebinding's subjects, roleref, labels, and annotations match the expected rolebinding -func computeReconciledRoleBinding(existing, expected *rbac.ClusterRoleBinding, removeExtraSubjects bool) (*ReconcileClusterRoleBindingResult, error) { +func computeReconciledRoleBinding(existing, expected RoleBinding, removeExtraSubjects bool) (*ReconcileClusterRoleBindingResult, error) { result := &ReconcileClusterRoleBindingResult{Operation: ReconcileNone} - result.Protected = (existing.Annotations[rbac.AutoUpdateAnnotationKey] == "false") + result.Protected = (existing.GetAnnotations()[rbac.AutoUpdateAnnotationKey] == "false") // Reset the binding completely if the roleRef is different - if expected.RoleRef != existing.RoleRef { + if expected.GetRoleRef() != existing.GetRoleRef() { result.RoleBinding = expected result.Operation = ReconcileRecreate return result, nil @@ -172,30 +188,30 @@ func computeReconciledRoleBinding(existing, expected *rbac.ClusterRoleBinding, r if err != nil { return nil, err } - result.RoleBinding = changedObj.(*rbac.ClusterRoleBinding) + result.RoleBinding = changedObj.(RoleBinding) // Merge expected annotations and labels - result.RoleBinding.Annotations = merge(expected.Annotations, result.RoleBinding.Annotations) - if !reflect.DeepEqual(result.RoleBinding.Annotations, existing.Annotations) { + result.RoleBinding.SetAnnotations(merge(expected.GetAnnotations(), result.RoleBinding.GetAnnotations())) + if !reflect.DeepEqual(result.RoleBinding.GetAnnotations(), existing.GetAnnotations()) { result.Operation = ReconcileUpdate } - result.RoleBinding.Labels = merge(expected.Labels, result.RoleBinding.Labels) - if !reflect.DeepEqual(result.RoleBinding.Labels, existing.Labels) { + result.RoleBinding.SetLabels(merge(expected.GetLabels(), result.RoleBinding.GetLabels())) + if !reflect.DeepEqual(result.RoleBinding.GetLabels(), existing.GetLabels()) { result.Operation = ReconcileUpdate } // Compute extra and missing subjects - result.MissingSubjects, result.ExtraSubjects = diffSubjectLists(expected.Subjects, existing.Subjects) + result.MissingSubjects, result.ExtraSubjects = diffSubjectLists(expected.GetSubjects(), existing.GetSubjects()) switch { case !removeExtraSubjects && len(result.MissingSubjects) > 0: // add missing subjects in the union case - result.RoleBinding.Subjects = append(result.RoleBinding.Subjects, result.MissingSubjects...) + result.RoleBinding.SetSubjects(append(result.RoleBinding.GetSubjects(), result.MissingSubjects...)) result.Operation = ReconcileUpdate case removeExtraSubjects && (len(result.MissingSubjects) > 0 || len(result.ExtraSubjects) > 0): // stomp to expected subjects in the non-union case - result.RoleBinding.Subjects = expected.Subjects + result.RoleBinding.SetSubjects(expected.GetSubjects()) result.Operation = ReconcileUpdate } diff --git a/pkg/registry/rbac/reconciliation/reconcile_clusterrolebindings_test.go b/pkg/registry/rbac/reconciliation/reconcile_rolebindings_test.go similarity index 93% rename from pkg/registry/rbac/reconciliation/reconcile_clusterrolebindings_test.go rename to pkg/registry/rbac/reconciliation/reconcile_rolebindings_test.go index e0839f94b6a..9a69e9136bf 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_clusterrolebindings_test.go +++ b/pkg/registry/rbac/reconciliation/reconcile_rolebindings_test.go @@ -161,13 +161,15 @@ func TestComputeUpdate(t *testing.T) { } for k, tc := range tests { - result, err := computeReconciledRoleBinding(tc.ActualBinding, tc.ExpectedBinding, tc.RemoveExtraSubjects) + actualRoleBinding := ClusterRoleBindingAdapter{ClusterRoleBinding: tc.ActualBinding} + expectedRoleBinding := ClusterRoleBindingAdapter{ClusterRoleBinding: tc.ExpectedBinding} + result, err := computeReconciledRoleBinding(actualRoleBinding, expectedRoleBinding, tc.RemoveExtraSubjects) if err != nil { t.Errorf("%s: %v", k, err) continue } updateNeeded := result.Operation != ReconcileNone - updatedBinding := result.RoleBinding + updatedBinding := result.RoleBinding.(ClusterRoleBindingAdapter).ClusterRoleBinding if updateNeeded != tc.ExpectedUpdateNeeded { t.Errorf("%s: Expected\n\t%v\ngot\n\t%v (%v)", k, tc.ExpectedUpdateNeeded, updateNeeded, result.Operation) continue diff --git a/pkg/registry/rbac/reconciliation/role_interfaces.go b/pkg/registry/rbac/reconciliation/role_interfaces.go index 05887cc0c83..b3bc3c882b0 100644 --- a/pkg/registry/rbac/reconciliation/role_interfaces.go +++ b/pkg/registry/rbac/reconciliation/role_interfaces.go @@ -79,7 +79,7 @@ func (c RoleModifier) Create(in RuleOwner) (RuleOwner, error) { } func (c RoleModifier) Update(in RuleOwner) (RuleOwner, error) { - ret, err := c.Client.Roles(in.GetNamespace()).Create(in.(RoleRuleOwner).Role) + ret, err := c.Client.Roles(in.GetNamespace()).Update(in.(RoleRuleOwner).Role) if err != nil { return nil, err } diff --git a/pkg/registry/rbac/reconciliation/rolebinding_interfaces.go b/pkg/registry/rbac/reconciliation/rolebinding_interfaces.go new file mode 100644 index 00000000000..6ec14ab2dbf --- /dev/null +++ b/pkg/registry/rbac/reconciliation/rolebinding_interfaces.go @@ -0,0 +1,101 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package reconciliation + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" +) + +type RoleBindingAdapter struct { + RoleBinding *rbac.RoleBinding +} + +func (o RoleBindingAdapter) GetNamespace() string { + return o.RoleBinding.Namespace +} + +func (o RoleBindingAdapter) GetName() string { + return o.RoleBinding.Name +} + +func (o RoleBindingAdapter) GetUID() types.UID { + return o.RoleBinding.UID +} + +func (o RoleBindingAdapter) GetLabels() map[string]string { + return o.RoleBinding.Labels +} + +func (o RoleBindingAdapter) SetLabels(in map[string]string) { + o.RoleBinding.Labels = in +} + +func (o RoleBindingAdapter) GetAnnotations() map[string]string { + return o.RoleBinding.Annotations +} + +func (o RoleBindingAdapter) SetAnnotations(in map[string]string) { + o.RoleBinding.Annotations = in +} + +func (o RoleBindingAdapter) GetRoleRef() rbac.RoleRef { + return o.RoleBinding.RoleRef +} + +func (o RoleBindingAdapter) GetSubjects() []rbac.Subject { + return o.RoleBinding.Subjects +} + +func (o RoleBindingAdapter) SetSubjects(in []rbac.Subject) { + o.RoleBinding.Subjects = in +} + +type RoleBindingClientAdapter struct { + Client internalversion.RoleBindingsGetter +} + +func (c RoleBindingClientAdapter) Get(namespace, name string) (RoleBinding, error) { + ret, err := c.Client.RoleBindings(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return RoleBindingAdapter{RoleBinding: ret}, err +} + +func (c RoleBindingClientAdapter) Create(in RoleBinding) (RoleBinding, error) { + ret, err := c.Client.RoleBindings(in.GetNamespace()).Create(in.(RoleBindingAdapter).RoleBinding) + if err != nil { + return nil, err + } + return RoleBindingAdapter{RoleBinding: ret}, err +} + +func (c RoleBindingClientAdapter) Update(in RoleBinding) (RoleBinding, error) { + ret, err := c.Client.RoleBindings(in.GetNamespace()).Update(in.(RoleBindingAdapter).RoleBinding) + if err != nil { + return nil, err + } + return RoleBindingAdapter{RoleBinding: ret}, err + +} + +func (c RoleBindingClientAdapter) Delete(namespace, name string, uid types.UID) error { + return c.Client.RoleBindings(namespace).Delete(name, &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}}) +} diff --git a/pkg/registry/rbac/rest/storage_rbac.go b/pkg/registry/rbac/rest/storage_rbac.go index 583270ab4c7..6ac39c2cb9d 100644 --- a/pkg/registry/rbac/rest/storage_rbac.go +++ b/pkg/registry/rbac/rest/storage_rbac.go @@ -147,7 +147,7 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { // ensure bootstrap roles are created or reconciled for _, clusterRole := range append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...) { - opts := reconciliation.ReconcileClusterRoleOptions{ + opts := reconciliation.ReconcileRoleOptions{ Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: &clusterRole}, Client: reconciliation.ClusterRoleModifier{Client: clientset.ClusterRoles()}, Confirm: true, @@ -175,9 +175,9 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { // ensure bootstrap rolebindings are created or reconciled for _, clusterRoleBinding := range append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...) { - opts := reconciliation.ReconcileClusterRoleBindingOptions{ - RoleBinding: &clusterRoleBinding, - Client: clientset.ClusterRoleBindings(), + opts := reconciliation.ReconcileRoleBindingOptions{ + RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: &clusterRoleBinding}, + Client: reconciliation.ClusterRoleBindingClientAdapter{Client: clientset.ClusterRoleBindings()}, Confirm: true, } err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { @@ -206,7 +206,7 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { // ensure bootstrap namespaced roles are created or reconciled for namespace, roles := range bootstrappolicy.NamespaceRoles() { for _, role := range roles { - opts := reconciliation.ReconcileClusterRoleOptions{ + opts := reconciliation.ReconcileRoleOptions{ Role: reconciliation.RoleRuleOwner{Role: &role}, Client: reconciliation.RoleModifier{Client: clientset}, Confirm: true, @@ -233,6 +233,38 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { } } + // ensure bootstrap namespaced rolebindings are created or reconciled + for namespace, roleBindings := range bootstrappolicy.NamespaceRoleBindings() { + for _, roleBinding := range roleBindings { + opts := reconciliation.ReconcileRoleBindingOptions{ + RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: &roleBinding}, + Client: reconciliation.RoleBindingClientAdapter{Client: clientset}, + Confirm: true, + } + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + result, err := opts.Run() + if err != nil { + return err + } + switch { + case result.Protected && result.Operation != reconciliation.ReconcileNone: + glog.Warningf("skipped reconcile-protected rolebinding.%s/%s in %v with missing subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects) + case result.Operation == reconciliation.ReconcileUpdate: + glog.Infof("updated rolebinding.%s/%s in %v with additional subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects) + case result.Operation == reconciliation.ReconcileCreate: + glog.Infof("created rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace) + case result.Operation == reconciliation.ReconcileRecreate: + glog.Infof("recreated rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace) + } + return nil + }) + if err != nil { + // don't fail on failures, try to create as many as you can + utilruntime.HandleError(fmt.Errorf("unable to reconcile rolebinding.%s/%s in %v: %v", rbac.GroupName, roleBinding.Name, namespace, err)) + } + } + } + return true, nil }) // if we're never able to make it through intialization, kill the API server diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go index 35b9cae165c..469d7eb10e0 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go @@ -28,6 +28,9 @@ import ( var ( // namespaceRoles is a map of namespace to slice of roles to create namespaceRoles = map[string][]rbac.Role{} + + // namespaceRoleBindings is a map of namespace to slice of roleBindings to create + namespaceRoleBindings = map[string][]rbac.RoleBinding{} ) func addNamespaceRole(namespace string, role rbac.Role) { @@ -48,6 +51,24 @@ func addNamespaceRole(namespace string, role rbac.Role) { namespaceRoles[namespace] = existingRoles } +func addNamespaceRoleBinding(namespace string, roleBinding rbac.RoleBinding) { + if !strings.HasPrefix(namespace, "kube-") { + glog.Fatalf(`roles can only be bootstrapped into reserved namespaces starting with "kube-", not %q`, namespace) + } + + existingRoleBindings := namespaceRoleBindings[namespace] + for _, existingRoleBinding := range existingRoleBindings { + if roleBinding.Name == existingRoleBinding.Name { + glog.Fatalf("rolebinding %q was already registered in %q", roleBinding.Name, namespace) + } + } + + roleBinding.Namespace = namespace + addDefaultMetadata(&roleBinding) + existingRoleBindings = append(existingRoleBindings, roleBinding) + namespaceRoleBindings[namespace] = existingRoleBindings +} + func init() { addNamespaceRole(metav1.NamespaceSystem, rbac.Role{ // role for finding authentication config info for starting a server @@ -57,9 +78,46 @@ func init() { rbac.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names("extension-apiserver-authentication").RuleOrDie(), }, }) + addNamespaceRole(metav1.NamespaceSystem, rbac.Role{ + // role for the bootstrap signer to be able to inspect kube-system secrets + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "bootstrap-signer"}, + Rules: []rbac.PolicyRule{ + rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("secrets").RuleOrDie(), + }, + }) + addNamespaceRole(metav1.NamespaceSystem, rbac.Role{ + // role for the token-cleaner to be able to remove secrets, but only in kube-system + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "token-cleaner"}, + Rules: []rbac.PolicyRule{ + rbac.NewRule("get", "list", "watch", "delete").Groups(legacyGroup).Resources("secrets").RuleOrDie(), + eventsRule(), + }, + }) + addNamespaceRoleBinding(metav1.NamespaceSystem, + rbac.NewRoleBinding(saRolePrefix+"bootstrap-signer", metav1.NamespaceSystem).SAs(metav1.NamespaceSystem, "bootstrap-signer").BindingOrDie()) + addNamespaceRoleBinding(metav1.NamespaceSystem, + rbac.NewRoleBinding(saRolePrefix+"token-cleaner", metav1.NamespaceSystem).SAs(metav1.NamespaceSystem, "token-cleaner").BindingOrDie()) + + addNamespaceRole(metav1.NamespacePublic, rbac.Role{ + // role for the bootstrap signer to be able to write its configmap + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "bootstrap-signer"}, + Rules: []rbac.PolicyRule{ + rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(), + rbac.NewRule("update").Groups(legacyGroup).Resources("configmaps").Names("cluster-info").RuleOrDie(), + eventsRule(), + }, + }) + addNamespaceRoleBinding(metav1.NamespacePublic, + rbac.NewRoleBinding(saRolePrefix+"bootstrap-signer", metav1.NamespacePublic).SAs(metav1.NamespaceSystem, "bootstrap-signer").BindingOrDie()) + } // NamespaceRoles returns a map of namespace to slice of roles to create func NamespaceRoles() map[string][]rbac.Role { return namespaceRoles } + +// NamespaceRoleBindings returns a map of namespace to slice of roles to create +func NamespaceRoleBindings() map[string][]rbac.RoleBinding { + return namespaceRoleBindings +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go index 3d944da94f0..d0697792370 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go @@ -187,6 +187,28 @@ func TestBootstrapNamespaceRoles(t *testing.T) { testObjects(t, list, "namespace-roles.yaml") } +func TestBootstrapNamespaceRoleBindings(t *testing.T) { + list := &api.List{} + names := sets.NewString() + roleBindings := map[string]runtime.Object{} + + namespaceRoleBindings := bootstrappolicy.NamespaceRoleBindings() + for _, namespace := range sets.StringKeySet(namespaceRoleBindings).List() { + bootstrapRoleBindings := namespaceRoleBindings[namespace] + for i := range bootstrapRoleBindings { + roleBinding := bootstrapRoleBindings[i] + names.Insert(roleBinding.Name) + roleBindings[roleBinding.Name] = &roleBinding + } + + for _, name := range names.List() { + list.Items = append(list.Items, roleBindings[name]) + } + } + + testObjects(t, list, "namespace-role-bindings.yaml") +} + func TestBootstrapClusterRoles(t *testing.T) { list := &api.List{} names := sets.NewString() diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-role-bindings.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-role-bindings.yaml new file mode 100644 index 00000000000..d14b53bcdb1 --- /dev/null +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-role-bindings.yaml @@ -0,0 +1,58 @@ +apiVersion: v1 +items: +- apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: RoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:bootstrap-signer + namespace: kube-public + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: system:controller:bootstrap-signer + subjects: + - kind: ServiceAccount + name: bootstrap-signer + namespace: kube-system +- apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: RoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:bootstrap-signer + namespace: kube-system + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: system:controller:bootstrap-signer + subjects: + - kind: ServiceAccount + name: bootstrap-signer + namespace: kube-system +- apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: RoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:token-cleaner + namespace: kube-system + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: system:controller:token-cleaner + subjects: + - kind: ServiceAccount + name: token-cleaner + namespace: kube-system +kind: List +metadata: {} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml index 6f78b9a0769..bd1a26045df 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml @@ -1,5 +1,40 @@ apiVersion: v1 items: +- apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: Role + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:bootstrap-signer + namespace: kube-public + rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - apiGroups: + - "" + resourceNames: + - cluster-info + resources: + - configmaps + verbs: + - update + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1beta1 kind: Role metadata: @@ -19,5 +54,52 @@ items: - configmaps verbs: - get +- apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: Role + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:bootstrap-signer + namespace: kube-system + rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: Role + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:token-cleaner + namespace: kube-system + rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - delete + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update kind: List metadata: {} diff --git a/staging/src/k8s.io/client-go/pkg/apis/rbac/helpers.go b/staging/src/k8s.io/client-go/pkg/apis/rbac/helpers.go index 24310d9d72f..a2ec9e8a53c 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/rbac/helpers.go +++ b/staging/src/k8s.io/client-go/pkg/apis/rbac/helpers.go @@ -279,6 +279,22 @@ func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder { } } +func NewRoleBindingForClusterRole(roleName, namespace string) *RoleBindingBuilder { + return &RoleBindingBuilder{ + RoleBinding: RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: namespace, + }, + RoleRef: RoleRef{ + APIGroup: GroupName, + Kind: "ClusterRole", + Name: roleName, + }, + }, + } +} + // Groups adds the specified groups as the subjects of the RoleBinding. func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder { for _, group := range groups {