mirror of https://github.com/k3s-io/k3s
Merge pull request #42259 from deads2k/rbac-07-reconcile-binding
Automatic merge from submit-queue Add RBAC roles for bootstrap controllers Supercedes https://github.com/kubernetes/kubernetes/pull/42221 When locking down controllers to individual RBAC roles we need to make sure that the bootstrap controllers have the right permissions. This adds the roles and bindings at the correct namespace scopes for the bootstrap-signer and token-cleaner controllers. @liggitt ptal @jbeda @luxas you got a good way to test this? It must not be covered in normal e2e or we'd've seen the issue before.pull/6/head
commit
01bfbb5fa0
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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}})
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}})
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
58
plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-role-bindings.yaml
vendored
Normal file
58
plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-role-bindings.yaml
vendored
Normal file
|
@ -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: {}
|
|
@ -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: {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue