mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #41155 from liggitt/reconcile
Automatic merge from submit-queue (batch tested with PRs 41378, 41413, 40743, 41155, 41385) Reconcile bootstrap clusterroles on server start Currently, on server start, bootstrap roles and bindings are only created if there are no existing roles or rolebindings. Instead, we should look at each bootstrap role and rolebinding, and ensure it exists and has required permissions and subjects at server start. This allows seamless upgrades to new versions that define roles for new controllers, or add permissions to existing roles. ```release-note Default RBAC ClusterRole and ClusterRoleBinding objects are automatically updated at server start to add missing permissions and subjects (extra permissions and subjects are left in place). To prevent autoupdating a particular role or rolebinding, annotate it with `rbac.authorization.kubernetes.io/autoupdate=false`. ```
This commit is contained in:
commit
ef042450fd
@ -34,6 +34,9 @@ const (
|
||||
GroupKind = "Group"
|
||||
ServiceAccountKind = "ServiceAccount"
|
||||
UserKind = "User"
|
||||
|
||||
// AutoUpdateAnnotationKey is the name of an annotation which prevents reconciliation if set to "false"
|
||||
AutoUpdateAnnotationKey = "rbac.authorization.kubernetes.io/autoupdate"
|
||||
)
|
||||
|
||||
// PolicyRule holds information that describes a policy rule, but does not contain information
|
||||
|
@ -34,6 +34,9 @@ const (
|
||||
GroupKind = "Group"
|
||||
ServiceAccountKind = "ServiceAccount"
|
||||
UserKind = "User"
|
||||
|
||||
// AutoUpdateAnnotationKey is the name of an annotation which prevents reconciliation if set to "false"
|
||||
AutoUpdateAnnotationKey = "rbac.authorization.kubernetes.io/autoupdate"
|
||||
)
|
||||
|
||||
// Authorization is calculated against
|
||||
|
@ -34,6 +34,9 @@ const (
|
||||
GroupKind = "Group"
|
||||
ServiceAccountKind = "ServiceAccount"
|
||||
UserKind = "User"
|
||||
|
||||
// AutoUpdateAnnotationKey is the name of an annotation which prevents reconciliation if set to "false"
|
||||
AutoUpdateAnnotationKey = "rbac.authorization.kubernetes.io/autoupdate"
|
||||
)
|
||||
|
||||
// Authorization is calculated against
|
||||
|
@ -33,6 +33,7 @@ filegroup(
|
||||
":package-srcs",
|
||||
"//pkg/registry/rbac/clusterrole:all-srcs",
|
||||
"//pkg/registry/rbac/clusterrolebinding:all-srcs",
|
||||
"//pkg/registry/rbac/reconciliation:all-srcs",
|
||||
"//pkg/registry/rbac/rest:all-srcs",
|
||||
"//pkg/registry/rbac/role:all-srcs",
|
||||
"//pkg/registry/rbac/rolebinding:all-srcs",
|
||||
|
54
pkg/registry/rbac/reconciliation/BUILD
Normal file
54
pkg/registry/rbac/reconciliation/BUILD
Normal file
@ -0,0 +1,54 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"reconcile_clusterrole_test.go",
|
||||
"reconcile_clusterrolebindings_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/rbac:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"reconcile_clusterrole.go",
|
||||
"reconcile_clusterrolebindings.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/rbac:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
|
||||
"//pkg/registry/rbac/validation:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
200
pkg/registry/rbac/reconciliation/reconcile_clusterrole.go
Normal file
200
pkg/registry/rbac/reconciliation/reconcile_clusterrole.go
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
type ReconcileOperation string
|
||||
|
||||
var (
|
||||
ReconcileCreate ReconcileOperation = "create"
|
||||
ReconcileUpdate ReconcileOperation = "update"
|
||||
ReconcileRecreate ReconcileOperation = "recreate"
|
||||
ReconcileNone ReconcileOperation = "none"
|
||||
)
|
||||
|
||||
type ReconcileClusterRoleOptions struct {
|
||||
// Role is the expected role that will be reconciled
|
||||
Role *rbac.ClusterRole
|
||||
// Confirm indicates writes should be performed. When false, results are returned as a dry-run.
|
||||
Confirm bool
|
||||
// RemoveExtraPermissions indicates reconciliation should remove extra permissions from an existing role
|
||||
RemoveExtraPermissions bool
|
||||
// Client is used to look up existing roles, and create/update the role when Confirm=true
|
||||
Client internalversion.ClusterRoleInterface
|
||||
}
|
||||
|
||||
type ReconcileClusterRoleResult struct {
|
||||
// Role is the reconciled role from the reconciliation operation.
|
||||
// If the reconcile was performed as a dry-run, or the existing role was protected, the reconciled role is not persisted.
|
||||
Role *rbac.ClusterRole
|
||||
|
||||
// MissingRules contains expected rules that were missing from the currently persisted role
|
||||
MissingRules []rbac.PolicyRule
|
||||
// ExtraRules contains extra permissions the currently persisted role had
|
||||
ExtraRules []rbac.PolicyRule
|
||||
|
||||
// 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.
|
||||
// If result.Protected == true, the role opted out of reconciliation, so the operation was not performed.
|
||||
// Otherwise, the operation was performed.
|
||||
Operation ReconcileOperation
|
||||
// Protected indicates an existing role prevented reconciliation
|
||||
Protected bool
|
||||
}
|
||||
|
||||
func (o *ReconcileClusterRoleOptions) Run() (*ReconcileClusterRoleResult, error) {
|
||||
return o.run(0)
|
||||
}
|
||||
|
||||
func (o *ReconcileClusterRoleOptions) 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 {
|
||||
return nil, fmt.Errorf("exceeded maximum attempts")
|
||||
}
|
||||
|
||||
var result *ReconcileClusterRoleResult
|
||||
|
||||
existing, err := o.Client.Get(o.Role.Name, metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
result = &ReconcileClusterRoleResult{
|
||||
Role: o.Role,
|
||||
MissingRules: o.Role.Rules,
|
||||
Operation: ReconcileCreate,
|
||||
}
|
||||
|
||||
case err != nil:
|
||||
return nil, err
|
||||
|
||||
default:
|
||||
result, err = computeReconciledRole(existing, o.Role, o.RemoveExtraPermissions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If reconcile-protected, short-circuit
|
||||
if result.Protected {
|
||||
return result, nil
|
||||
}
|
||||
// If we're in dry-run mode, short-circuit
|
||||
if !o.Confirm {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
switch result.Operation {
|
||||
case ReconcileCreate:
|
||||
created, err := o.Client.Create(result.Role)
|
||||
// If created since we started this reconcile, re-run
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return o.run(attempts + 1)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Role = created
|
||||
|
||||
case ReconcileUpdate:
|
||||
updated, err := o.Client.Update(result.Role)
|
||||
// If deleted since we started this reconcile, re-run
|
||||
if errors.IsNotFound(err) {
|
||||
return o.run(attempts + 1)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Role = updated
|
||||
|
||||
case ReconcileNone:
|
||||
// no-op
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operation: %v", result.Operation)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// computeReconciledRole returns the role that must be created and/or updated to make the
|
||||
// existing role's permissions match the expected role's permissions
|
||||
func computeReconciledRole(existing, expected *rbac.ClusterRole, removeExtraPermissions bool) (*ReconcileClusterRoleResult, error) {
|
||||
result := &ReconcileClusterRoleResult{Operation: ReconcileNone}
|
||||
|
||||
result.Protected = (existing.Annotations[rbac.AutoUpdateAnnotationKey] == "false")
|
||||
|
||||
// Start with a copy of the existing object
|
||||
changedObj, err := api.Scheme.DeepCopy(existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Role = changedObj.(*rbac.ClusterRole)
|
||||
|
||||
// Merge expected annotations and labels
|
||||
result.Role.Annotations = merge(expected.Annotations, result.Role.Annotations)
|
||||
if !reflect.DeepEqual(result.Role.Annotations, existing.Annotations) {
|
||||
result.Operation = ReconcileUpdate
|
||||
}
|
||||
result.Role.Labels = merge(expected.Labels, result.Role.Labels)
|
||||
if !reflect.DeepEqual(result.Role.Labels, existing.Labels) {
|
||||
result.Operation = ReconcileUpdate
|
||||
}
|
||||
|
||||
// Compute extra and missing rules
|
||||
_, result.ExtraRules = validation.Covers(expected.Rules, existing.Rules)
|
||||
_, result.MissingRules = validation.Covers(existing.Rules, expected.Rules)
|
||||
|
||||
switch {
|
||||
case !removeExtraPermissions && len(result.MissingRules) > 0:
|
||||
// add missing rules in the union case
|
||||
result.Role.Rules = append(result.Role.Rules, result.MissingRules...)
|
||||
result.Operation = ReconcileUpdate
|
||||
|
||||
case removeExtraPermissions && (len(result.MissingRules) > 0 || len(result.ExtraRules) > 0):
|
||||
// stomp to expected rules in the non-union case
|
||||
result.Role.Rules = expected.Rules
|
||||
result.Operation = ReconcileUpdate
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// merge combines the given maps with the later annotations having higher precedence
|
||||
func merge(maps ...map[string]string) map[string]string {
|
||||
var output map[string]string = nil
|
||||
for _, m := range maps {
|
||||
if m != nil && output == nil {
|
||||
output = map[string]string{}
|
||||
}
|
||||
for k, v := range m {
|
||||
output[k] = v
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
273
pkg/registry/rbac/reconciliation/reconcile_clusterrole_test.go
Normal file
273
pkg/registry/rbac/reconciliation/reconcile_clusterrole_test.go
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"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}}
|
||||
}
|
||||
|
||||
func rules(resources ...string) []rbac.PolicyRule {
|
||||
r := []rbac.PolicyRule{}
|
||||
for _, resource := range resources {
|
||||
r = append(r, rbac.PolicyRule{APIGroups: []string{""}, Verbs: []string{"get"}, Resources: []string{resource}})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type ss map[string]string
|
||||
|
||||
func TestComputeReconciledRole(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
expectedRole *rbac.ClusterRole
|
||||
actualRole *rbac.ClusterRole
|
||||
removeExtraPermissions bool
|
||||
|
||||
expectedReconciledRole *rbac.ClusterRole
|
||||
expectedReconciliationNeeded bool
|
||||
}{
|
||||
"empty": {
|
||||
expectedRole: role(rules(), nil, nil),
|
||||
actualRole: role(rules(), nil, nil),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: nil,
|
||||
expectedReconciliationNeeded: false,
|
||||
},
|
||||
"match without union": {
|
||||
expectedRole: role(rules("a"), nil, nil),
|
||||
actualRole: role(rules("a"), nil, nil),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: nil,
|
||||
expectedReconciliationNeeded: false,
|
||||
},
|
||||
"match with union": {
|
||||
expectedRole: role(rules("a"), nil, nil),
|
||||
actualRole: role(rules("a"), nil, nil),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: nil,
|
||||
expectedReconciliationNeeded: false,
|
||||
},
|
||||
"different rules without union": {
|
||||
expectedRole: role(rules("a"), nil, nil),
|
||||
actualRole: role(rules("b"), nil, nil),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), nil, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"different rules with union": {
|
||||
expectedRole: role(rules("a"), nil, nil),
|
||||
actualRole: role(rules("b"), nil, nil),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("b", "a"), nil, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"match labels without union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: nil,
|
||||
expectedReconciliationNeeded: false,
|
||||
},
|
||||
"match labels with union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: nil,
|
||||
expectedReconciliationNeeded: false,
|
||||
},
|
||||
"different labels without union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("a"), ss{"2": "b"}, nil),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), ss{"1": "a", "2": "b"}, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"different labels with union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("a"), ss{"2": "b"}, nil),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), ss{"1": "a", "2": "b"}, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"different labels and rules without union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("b"), ss{"2": "b"}, nil),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), ss{"1": "a", "2": "b"}, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"different labels and rules with union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("b"), ss{"2": "b"}, nil),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("b", "a"), ss{"1": "a", "2": "b"}, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"conflicting labels and rules without union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("b"), ss{"1": "b"}, nil),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), ss{"1": "b"}, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"conflicting labels and rules with union": {
|
||||
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
|
||||
actualRole: role(rules("b"), ss{"1": "b"}, nil),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("b", "a"), ss{"1": "b"}, nil),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"match annotations without union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: nil,
|
||||
expectedReconciliationNeeded: false,
|
||||
},
|
||||
"match annotations with union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: nil,
|
||||
expectedReconciliationNeeded: false,
|
||||
},
|
||||
"different annotations without union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("a"), nil, ss{"2": "b"}),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), nil, ss{"1": "a", "2": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"different annotations with union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("a"), nil, ss{"2": "b"}),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), nil, ss{"1": "a", "2": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"different annotations and rules without union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("b"), nil, ss{"2": "b"}),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), nil, ss{"1": "a", "2": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"different annotations and rules with union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("b"), nil, ss{"2": "b"}),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("b", "a"), nil, ss{"1": "a", "2": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"conflicting annotations and rules without union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("b"), nil, ss{"1": "b"}),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), nil, ss{"1": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"conflicting annotations and rules with union": {
|
||||
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
|
||||
actualRole: role(rules("b"), nil, ss{"1": "b"}),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("b", "a"), nil, ss{"1": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"conflicting labels/annotations and rules without union": {
|
||||
expectedRole: role(rules("a"), ss{"3": "d"}, ss{"1": "a"}),
|
||||
actualRole: role(rules("b"), ss{"4": "e"}, ss{"1": "b"}),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(rules("a"), ss{"3": "d", "4": "e"}, ss{"1": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"conflicting labels/annotations and rules with union": {
|
||||
expectedRole: role(rules("a"), ss{"3": "d"}, ss{"1": "a"}),
|
||||
actualRole: role(rules("b"), ss{"4": "e"}, ss{"1": "b"}),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(rules("b", "a"), ss{"3": "d", "4": "e"}, ss{"1": "b"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"complex labels/annotations and rules without union": {
|
||||
expectedRole: role(rules("pods", "nodes", "secrets"), ss{"env": "prod", "color": "blue"}, ss{"description": "fancy", "system": "true"}),
|
||||
actualRole: role(rules("nodes", "images", "projects"), ss{"color": "red", "team": "pm"}, ss{"system": "false", "owner": "admin", "vip": "yes"}),
|
||||
removeExtraPermissions: true,
|
||||
|
||||
expectedReconciledRole: role(
|
||||
rules("pods", "nodes", "secrets"),
|
||||
ss{"env": "prod", "color": "red", "team": "pm"},
|
||||
ss{"description": "fancy", "system": "false", "owner": "admin", "vip": "yes"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
"complex labels/annotations and rules with union": {
|
||||
expectedRole: role(rules("pods", "nodes", "secrets"), ss{"env": "prod", "color": "blue", "manager": "randy"}, ss{"description": "fancy", "system": "true", "up": "true"}),
|
||||
actualRole: role(rules("nodes", "images", "projects"), ss{"color": "red", "team": "pm"}, ss{"system": "false", "owner": "admin", "vip": "yes", "rate": "down"}),
|
||||
removeExtraPermissions: false,
|
||||
|
||||
expectedReconciledRole: role(
|
||||
rules("nodes", "images", "projects", "pods", "secrets"),
|
||||
ss{"env": "prod", "manager": "randy", "color": "red", "team": "pm"},
|
||||
ss{"description": "fancy", "system": "false", "owner": "admin", "vip": "yes", "rate": "down", "up": "true"}),
|
||||
expectedReconciliationNeeded: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range tests {
|
||||
result, err := computeReconciledRole(tc.actualRole, tc.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 && !api.Semantic.DeepEqual(result.Role, tc.expectedReconciledRole) {
|
||||
t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, tc.expectedReconciledRole, result.Role)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,234 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
// ReconcileClusterRoleBindingOptions holds options for running a role binding reconciliation
|
||||
type ReconcileClusterRoleBindingOptions struct {
|
||||
// RoleBinding is the expected rolebinding that will be reconciled
|
||||
RoleBinding *rbac.ClusterRoleBinding
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// MissingSubjects contains expected subjects that were missing from the currently persisted rolebinding
|
||||
MissingSubjects []rbac.Subject
|
||||
// ExtraSubjects contains extra subjects the currently persisted rolebinding had
|
||||
ExtraSubjects []rbac.Subject
|
||||
|
||||
// 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.
|
||||
// If result.Protected == true, the rolebinding opted out of reconciliation, so the operation was not performed.
|
||||
// Otherwise, the operation was performed.
|
||||
Operation ReconcileOperation
|
||||
// Protected indicates an existing role prevented reconciliation
|
||||
Protected bool
|
||||
}
|
||||
|
||||
func (o *ReconcileClusterRoleBindingOptions) Run() (*ReconcileClusterRoleBindingResult, error) {
|
||||
return o.run(0)
|
||||
}
|
||||
|
||||
func (o *ReconcileClusterRoleBindingOptions) 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 {
|
||||
return nil, fmt.Errorf("exceeded maximum attempts")
|
||||
}
|
||||
|
||||
var result *ReconcileClusterRoleBindingResult
|
||||
|
||||
existingBinding, err := o.Client.Get(o.RoleBinding.Name, metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
result = &ReconcileClusterRoleBindingResult{
|
||||
RoleBinding: o.RoleBinding,
|
||||
MissingSubjects: o.RoleBinding.Subjects,
|
||||
Operation: ReconcileCreate,
|
||||
}
|
||||
|
||||
case err != nil:
|
||||
return nil, err
|
||||
|
||||
default:
|
||||
result, err = computeReconciledRoleBinding(existingBinding, o.RoleBinding, o.RemoveExtraSubjects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If reconcile-protected, short-circuit
|
||||
if result.Protected {
|
||||
return result, nil
|
||||
}
|
||||
// If we're in dry-run mode, short-circuit
|
||||
if !o.Confirm {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
switch result.Operation {
|
||||
case ReconcileRecreate:
|
||||
// Try deleting
|
||||
err := o.Client.Delete(
|
||||
existingBinding.Name,
|
||||
&metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &existingBinding.UID}},
|
||||
)
|
||||
switch {
|
||||
case err == nil, errors.IsNotFound(err):
|
||||
// object no longer exists, as desired
|
||||
case errors.IsConflict(err):
|
||||
// delete failed because our UID precondition conflicted
|
||||
// this could mean another object exists with a different UID, re-run
|
||||
return o.run(attempts + 1)
|
||||
default:
|
||||
// return other errors
|
||||
return nil, err
|
||||
}
|
||||
// continue to create
|
||||
fallthrough
|
||||
case ReconcileCreate:
|
||||
created, err := o.Client.Create(result.RoleBinding)
|
||||
// If created since we started this reconcile, re-run
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return o.run(attempts + 1)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.RoleBinding = created
|
||||
|
||||
case ReconcileUpdate:
|
||||
updated, err := o.Client.Update(result.RoleBinding)
|
||||
// If deleted since we started this reconcile, re-run
|
||||
if errors.IsNotFound(err) {
|
||||
return o.run(attempts + 1)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.RoleBinding = updated
|
||||
|
||||
case ReconcileNone:
|
||||
// no-op
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operation: %v", result.Operation)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
result := &ReconcileClusterRoleBindingResult{Operation: ReconcileNone}
|
||||
|
||||
result.Protected = (existing.Annotations[rbac.AutoUpdateAnnotationKey] == "false")
|
||||
|
||||
// Reset the binding completely if the roleRef is different
|
||||
if expected.RoleRef != existing.RoleRef {
|
||||
result.RoleBinding = expected
|
||||
result.Operation = ReconcileRecreate
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Start with a copy of the existing object
|
||||
changedObj, err := api.Scheme.DeepCopy(existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.RoleBinding = changedObj.(*rbac.ClusterRoleBinding)
|
||||
|
||||
// Merge expected annotations and labels
|
||||
result.RoleBinding.Annotations = merge(expected.Annotations, result.RoleBinding.Annotations)
|
||||
if !reflect.DeepEqual(result.RoleBinding.Annotations, existing.Annotations) {
|
||||
result.Operation = ReconcileUpdate
|
||||
}
|
||||
result.RoleBinding.Labels = merge(expected.Labels, result.RoleBinding.Labels)
|
||||
if !reflect.DeepEqual(result.RoleBinding.Labels, existing.Labels) {
|
||||
result.Operation = ReconcileUpdate
|
||||
}
|
||||
|
||||
// Compute extra and missing subjects
|
||||
result.MissingSubjects, result.ExtraSubjects = diffSubjectLists(expected.Subjects, existing.Subjects)
|
||||
|
||||
switch {
|
||||
case !removeExtraSubjects && len(result.MissingSubjects) > 0:
|
||||
// add missing subjects in the union case
|
||||
result.RoleBinding.Subjects = append(result.RoleBinding.Subjects, 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.Operation = ReconcileUpdate
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func contains(list []rbac.Subject, item rbac.Subject) bool {
|
||||
for _, listItem := range list {
|
||||
if listItem == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// diffSubjectLists returns lists containing the items unique to each provided list:
|
||||
// list1Only = list1 - list2
|
||||
// list2Only = list2 - list1
|
||||
// if both returned lists are empty, the provided lists are equal
|
||||
func diffSubjectLists(list1 []rbac.Subject, list2 []rbac.Subject) (list1Only []rbac.Subject, list2Only []rbac.Subject) {
|
||||
for _, list1Item := range list1 {
|
||||
if !contains(list2, list1Item) {
|
||||
if !contains(list1Only, list1Item) {
|
||||
list1Only = append(list1Only, list1Item)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, list2Item := range list2 {
|
||||
if !contains(list1, list2Item) {
|
||||
if !contains(list2Only, list2Item) {
|
||||
list2Only = append(list2Only, list2Item)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
api "k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
)
|
||||
|
||||
func binding(roleRef rbac.RoleRef, subjects []rbac.Subject) *rbac.ClusterRoleBinding {
|
||||
return &rbac.ClusterRoleBinding{RoleRef: roleRef, Subjects: subjects}
|
||||
}
|
||||
|
||||
func ref(name string) rbac.RoleRef {
|
||||
return rbac.RoleRef{Name: name}
|
||||
}
|
||||
|
||||
func subject(name string) rbac.Subject {
|
||||
return rbac.Subject{Name: name}
|
||||
}
|
||||
|
||||
func subjects(names ...string) []rbac.Subject {
|
||||
r := []rbac.Subject{}
|
||||
for _, name := range names {
|
||||
r = append(r, subject(name))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func TestDiffObjectReferenceLists(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
A []rbac.Subject
|
||||
B []rbac.Subject
|
||||
ExpectedOnlyA []rbac.Subject
|
||||
ExpectedOnlyB []rbac.Subject
|
||||
}{
|
||||
"empty": {},
|
||||
|
||||
"matching, order-independent": {
|
||||
A: subjects("foo", "bar"),
|
||||
B: subjects("bar", "foo"),
|
||||
},
|
||||
|
||||
"partial match": {
|
||||
A: subjects("foo", "bar"),
|
||||
B: subjects("foo", "baz"),
|
||||
ExpectedOnlyA: subjects("bar"),
|
||||
ExpectedOnlyB: subjects("baz"),
|
||||
},
|
||||
|
||||
"missing": {
|
||||
A: subjects("foo"),
|
||||
B: subjects("bar"),
|
||||
ExpectedOnlyA: subjects("foo"),
|
||||
ExpectedOnlyB: subjects("bar"),
|
||||
},
|
||||
|
||||
"remove duplicates": {
|
||||
A: subjects("foo", "foo"),
|
||||
B: subjects("bar", "bar"),
|
||||
ExpectedOnlyA: subjects("foo"),
|
||||
ExpectedOnlyB: subjects("bar"),
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range tests {
|
||||
onlyA, onlyB := diffSubjectLists(tc.A, tc.B)
|
||||
if !api.Semantic.DeepEqual(onlyA, tc.ExpectedOnlyA) {
|
||||
t.Errorf("%s: Expected %#v, got %#v", k, tc.ExpectedOnlyA, onlyA)
|
||||
}
|
||||
if !api.Semantic.DeepEqual(onlyB, tc.ExpectedOnlyB) {
|
||||
t.Errorf("%s: Expected %#v, got %#v", k, tc.ExpectedOnlyB, onlyB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeUpdate(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
ExpectedBinding *rbac.ClusterRoleBinding
|
||||
ActualBinding *rbac.ClusterRoleBinding
|
||||
RemoveExtraSubjects bool
|
||||
|
||||
ExpectedUpdatedBinding *rbac.ClusterRoleBinding
|
||||
ExpectedUpdateNeeded bool
|
||||
}{
|
||||
"match without union": {
|
||||
ExpectedBinding: binding(ref("role"), subjects("a")),
|
||||
ActualBinding: binding(ref("role"), subjects("a")),
|
||||
RemoveExtraSubjects: true,
|
||||
|
||||
ExpectedUpdatedBinding: nil,
|
||||
ExpectedUpdateNeeded: false,
|
||||
},
|
||||
"match with union": {
|
||||
ExpectedBinding: binding(ref("role"), subjects("a")),
|
||||
ActualBinding: binding(ref("role"), subjects("a")),
|
||||
RemoveExtraSubjects: false,
|
||||
|
||||
ExpectedUpdatedBinding: nil,
|
||||
ExpectedUpdateNeeded: false,
|
||||
},
|
||||
|
||||
"different roleref with identical subjects": {
|
||||
ExpectedBinding: binding(ref("role"), subjects("a")),
|
||||
ActualBinding: binding(ref("differentRole"), subjects("a")),
|
||||
RemoveExtraSubjects: false,
|
||||
|
||||
ExpectedUpdatedBinding: binding(ref("role"), subjects("a")),
|
||||
ExpectedUpdateNeeded: true,
|
||||
},
|
||||
|
||||
"extra subjects without union": {
|
||||
ExpectedBinding: binding(ref("role"), subjects("a")),
|
||||
ActualBinding: binding(ref("role"), subjects("a", "b")),
|
||||
RemoveExtraSubjects: true,
|
||||
|
||||
ExpectedUpdatedBinding: binding(ref("role"), subjects("a")),
|
||||
ExpectedUpdateNeeded: true,
|
||||
},
|
||||
"extra subjects with union": {
|
||||
ExpectedBinding: binding(ref("role"), subjects("a")),
|
||||
ActualBinding: binding(ref("role"), subjects("a", "b")),
|
||||
RemoveExtraSubjects: false,
|
||||
|
||||
ExpectedUpdatedBinding: nil,
|
||||
ExpectedUpdateNeeded: false,
|
||||
},
|
||||
|
||||
"missing subjects without union": {
|
||||
ExpectedBinding: binding(ref("role"), subjects("a", "c")),
|
||||
ActualBinding: binding(ref("role"), subjects("a", "b")),
|
||||
RemoveExtraSubjects: true,
|
||||
|
||||
ExpectedUpdatedBinding: binding(ref("role"), subjects("a", "c")),
|
||||
ExpectedUpdateNeeded: true,
|
||||
},
|
||||
"missing subjects with union": {
|
||||
ExpectedBinding: binding(ref("role"), subjects("a", "c")),
|
||||
ActualBinding: binding(ref("role"), subjects("a", "b")),
|
||||
RemoveExtraSubjects: false,
|
||||
|
||||
ExpectedUpdatedBinding: binding(ref("role"), subjects("a", "b", "c")),
|
||||
ExpectedUpdateNeeded: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range tests {
|
||||
result, err := computeReconciledRoleBinding(tc.ActualBinding, tc.ExpectedBinding, tc.RemoveExtraSubjects)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", k, err)
|
||||
continue
|
||||
}
|
||||
updateNeeded := result.Operation != ReconcileNone
|
||||
updatedBinding := result.RoleBinding
|
||||
if updateNeeded != tc.ExpectedUpdateNeeded {
|
||||
t.Errorf("%s: Expected\n\t%v\ngot\n\t%v (%v)", k, tc.ExpectedUpdateNeeded, updateNeeded, result.Operation)
|
||||
continue
|
||||
}
|
||||
if updateNeeded && !api.Semantic.DeepEqual(updatedBinding, tc.ExpectedUpdatedBinding) {
|
||||
t.Errorf("%s: Expected\n\t%v %v\ngot\n\t%v %v", k, tc.ExpectedUpdatedBinding.RoleRef, tc.ExpectedUpdatedBinding.Subjects, updatedBinding.RoleRef, updatedBinding.Subjects)
|
||||
}
|
||||
}
|
||||
}
|
@ -17,12 +17,14 @@ go_library(
|
||||
"//pkg/apis/rbac/v1alpha1:go_default_library",
|
||||
"//pkg/apis/rbac/v1beta1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
|
||||
"//pkg/client/retry:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrole:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrole/policybased:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrole/storage:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrolebinding:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrolebinding/policybased:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrolebinding/storage:go_default_library",
|
||||
"//pkg/registry/rbac/reconciliation:go_default_library",
|
||||
"//pkg/registry/rbac/role:go_default_library",
|
||||
"//pkg/registry/rbac/role/policybased:go_default_library",
|
||||
"//pkg/registry/rbac/role/storage:go_default_library",
|
||||
@ -32,7 +34,6 @@ go_library(
|
||||
"//pkg/registry/rbac/validation:go_default_library",
|
||||
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
@ -36,12 +35,14 @@ import (
|
||||
rbacapiv1alpha1 "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1"
|
||||
rbacapiv1beta1 "k8s.io/kubernetes/pkg/apis/rbac/v1beta1"
|
||||
rbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
|
||||
"k8s.io/kubernetes/pkg/client/retry"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/clusterrole"
|
||||
clusterrolepolicybased "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/policybased"
|
||||
clusterrolestore "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"
|
||||
clusterrolebindingpolicybased "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/policybased"
|
||||
clusterrolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/reconciliation"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/role"
|
||||
rolepolicybased "k8s.io/kubernetes/pkg/registry/rbac/role/policybased"
|
||||
rolestore "k8s.io/kubernetes/pkg/registry/rbac/role/storage"
|
||||
@ -133,37 +134,62 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
existingClusterRoles, err := clientset.ClusterRoles().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err))
|
||||
return false, nil
|
||||
}
|
||||
// only initialized on empty etcd
|
||||
if len(existingClusterRoles.Items) == 0 {
|
||||
for _, clusterRole := range append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...) {
|
||||
if _, err := clientset.ClusterRoles().Create(&clusterRole); err != nil {
|
||||
// don't fail on failures, try to create as many as you can
|
||||
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err))
|
||||
continue
|
||||
// ensure bootstrap roles are created or reconciled
|
||||
for _, clusterRole := range append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...) {
|
||||
opts := reconciliation.ReconcileClusterRoleOptions{
|
||||
Role: &clusterRole,
|
||||
Client: clientset.ClusterRoles(),
|
||||
Confirm: true,
|
||||
}
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
result, err := opts.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("Created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name)
|
||||
switch {
|
||||
case result.Protected && result.Operation != reconciliation.ReconcileNone:
|
||||
glog.Warningf("skipped reconcile-protected clusterrole.%s/%s with missing permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules)
|
||||
case result.Operation == reconciliation.ReconcileUpdate:
|
||||
glog.Infof("updated clusterrole.%s/%s with additional permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules)
|
||||
case result.Operation == reconciliation.ReconcileCreate:
|
||||
glog.Infof("created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name)
|
||||
}
|
||||
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 clusterrole.%s/%s: %v", rbac.GroupName, clusterRole.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
existingClusterRoleBindings, err := clientset.ClusterRoleBindings().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterrolebindings: %v", err))
|
||||
return false, nil
|
||||
}
|
||||
// only initialized on empty etcd
|
||||
if len(existingClusterRoleBindings.Items) == 0 {
|
||||
for _, clusterRoleBinding := range append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...) {
|
||||
if _, err := clientset.ClusterRoleBindings().Create(&clusterRoleBinding); err != nil {
|
||||
// don't fail on failures, try to create as many as you can
|
||||
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterrolebindings: %v", err))
|
||||
continue
|
||||
// ensure bootstrap rolebindings are created or reconciled
|
||||
for _, clusterRoleBinding := range append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...) {
|
||||
|
||||
opts := reconciliation.ReconcileClusterRoleBindingOptions{
|
||||
RoleBinding: &clusterRoleBinding,
|
||||
Client: clientset.ClusterRoleBindings(),
|
||||
Confirm: true,
|
||||
}
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
result, err := opts.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("Created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
|
||||
switch {
|
||||
case result.Protected && result.Operation != reconciliation.ReconcileNone:
|
||||
glog.Warningf("skipped reconcile-protected clusterrolebinding.%s/%s with missing subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects)
|
||||
case result.Operation == reconciliation.ReconcileUpdate:
|
||||
glog.Infof("updated clusterrolebinding.%s/%s with additional subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects)
|
||||
case result.Operation == reconciliation.ReconcileCreate:
|
||||
glog.Infof("created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
|
||||
case result.Operation == reconciliation.ReconcileRecreate:
|
||||
glog.Infof("recreated clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
|
||||
}
|
||||
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 clusterrolebinding.%s/%s: %v", rbac.GroupName, clusterRoleBinding.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,8 @@ var (
|
||||
ReadWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"}
|
||||
Read = []string{"get", "list", "watch"}
|
||||
|
||||
Label = map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}
|
||||
Label = map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}
|
||||
Annotation = map[string]string{rbac.AutoUpdateAnnotationKey: "true"}
|
||||
)
|
||||
|
||||
const (
|
||||
@ -51,6 +52,13 @@ func addClusterRoleLabel(roles []rbac.ClusterRole) {
|
||||
for k, v := range Label {
|
||||
roles[i].ObjectMeta.Labels[k] = v
|
||||
}
|
||||
|
||||
if roles[i].ObjectMeta.Annotations == nil {
|
||||
roles[i].ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
for k, v := range Annotation {
|
||||
roles[i].ObjectMeta.Annotations[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -63,6 +71,13 @@ func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) {
|
||||
for k, v := range Label {
|
||||
rolebindings[i].ObjectMeta.Labels[k] = v
|
||||
}
|
||||
|
||||
if rolebindings[i].ObjectMeta.Annotations == nil {
|
||||
rolebindings[i].ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
for k, v := range Annotation {
|
||||
rolebindings[i].ObjectMeta.Annotations[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -18,6 +20,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -36,6 +40,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -54,6 +60,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -69,6 +77,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -84,6 +94,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
|
@ -3,6 +3,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -159,6 +161,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -177,6 +181,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -313,6 +319,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -333,6 +341,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -347,6 +357,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -365,6 +377,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -384,6 +398,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -401,6 +417,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -506,6 +524,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -618,6 +638,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -635,6 +657,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -663,6 +687,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -693,6 +719,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -742,6 +770,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
|
@ -3,6 +3,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -18,6 +20,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -33,6 +37,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -48,6 +54,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -63,6 +71,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -78,6 +88,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -93,6 +105,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -108,6 +122,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -123,6 +139,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -138,6 +156,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -153,6 +173,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -168,6 +190,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -183,6 +207,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -198,6 +224,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -213,6 +241,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -228,6 +258,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -243,6 +275,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -258,6 +292,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -273,6 +309,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -288,6 +326,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -303,6 +343,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -318,6 +360,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
|
@ -3,6 +3,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -49,6 +51,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -80,6 +84,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -129,6 +135,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -181,6 +189,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -233,6 +243,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -295,6 +307,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -336,6 +350,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -363,6 +379,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -439,6 +457,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -479,6 +499,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -512,6 +534,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -556,6 +580,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -643,6 +669,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -665,6 +693,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -706,6 +736,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -747,6 +779,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -776,6 +810,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -805,6 +841,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -827,6 +865,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -864,6 +904,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
@ -917,6 +959,8 @@ items:
|
||||
- apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
|
Loading…
Reference in New Issue
Block a user