diff --git a/pkg/registry/rbac/reconciliation/BUILD b/pkg/registry/rbac/reconciliation/BUILD index b2c9a10d5d5..79a1484a3d1 100644 --- a/pkg/registry/rbac/reconciliation/BUILD +++ b/pkg/registry/rbac/reconciliation/BUILD @@ -11,8 +11,8 @@ load( go_test( name = "go_default_test", srcs = [ - "reconcile_clusterrole_test.go", "reconcile_clusterrolebindings_test.go", + "reconcile_role_test.go", ], library = ":go_default_library", tags = ["automanaged"], @@ -26,8 +26,9 @@ go_test( go_library( name = "go_default_library", srcs = [ - "reconcile_clusterrole.go", "reconcile_clusterrolebindings.go", + "reconcile_role.go", + "role_interfaces.go", ], tags = ["automanaged"], deps = [ diff --git a/pkg/registry/rbac/reconciliation/reconcile_clusterrole.go b/pkg/registry/rbac/reconciliation/reconcile_role.go similarity index 100% rename from pkg/registry/rbac/reconciliation/reconcile_clusterrole.go rename to pkg/registry/rbac/reconciliation/reconcile_role.go diff --git a/pkg/registry/rbac/reconciliation/reconcile_clusterrole_test.go b/pkg/registry/rbac/reconciliation/reconcile_role_test.go similarity index 100% rename from pkg/registry/rbac/reconciliation/reconcile_clusterrole_test.go rename to pkg/registry/rbac/reconciliation/reconcile_role_test.go diff --git a/pkg/registry/rbac/reconciliation/role_interfaces.go b/pkg/registry/rbac/reconciliation/role_interfaces.go new file mode 100644 index 00000000000..05887cc0c83 --- /dev/null +++ b/pkg/registry/rbac/reconciliation/role_interfaces.go @@ -0,0 +1,88 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package reconciliation + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" +) + +type RoleRuleOwner struct { + Role *rbac.Role +} + +func (o RoleRuleOwner) GetNamespace() string { + return o.Role.Namespace +} + +func (o RoleRuleOwner) GetName() string { + return o.Role.Name +} + +func (o RoleRuleOwner) GetLabels() map[string]string { + return o.Role.Labels +} + +func (o RoleRuleOwner) SetLabels(in map[string]string) { + o.Role.Labels = in +} + +func (o RoleRuleOwner) GetAnnotations() map[string]string { + return o.Role.Annotations +} + +func (o RoleRuleOwner) SetAnnotations(in map[string]string) { + o.Role.Annotations = in +} + +func (o RoleRuleOwner) GetRules() []rbac.PolicyRule { + return o.Role.Rules +} + +func (o RoleRuleOwner) SetRules(in []rbac.PolicyRule) { + o.Role.Rules = in +} + +type RoleModifier struct { + Client internalversion.RolesGetter +} + +func (c RoleModifier) Get(namespace, name string) (RuleOwner, error) { + ret, err := c.Client.Roles(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return RoleRuleOwner{Role: ret}, err +} + +func (c RoleModifier) Create(in RuleOwner) (RuleOwner, error) { + ret, err := c.Client.Roles(in.GetNamespace()).Create(in.(RoleRuleOwner).Role) + if err != nil { + return nil, err + } + return RoleRuleOwner{Role: ret}, err +} + +func (c RoleModifier) Update(in RuleOwner) (RuleOwner, error) { + ret, err := c.Client.Roles(in.GetNamespace()).Create(in.(RoleRuleOwner).Role) + if err != nil { + return nil, err + } + return RoleRuleOwner{Role: ret}, err + +} diff --git a/pkg/registry/rbac/rest/storage_rbac.go b/pkg/registry/rbac/rest/storage_rbac.go index f4a3d9c06eb..867011296ea 100644 --- a/pkg/registry/rbac/rest/storage_rbac.go +++ b/pkg/registry/rbac/rest/storage_rbac.go @@ -165,7 +165,6 @@ 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(), @@ -194,6 +193,36 @@ 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{ + Role: reconciliation.RoleRuleOwner{Role: &role}, + Client: reconciliation.RoleModifier{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 role.%s/%s in %v with missing permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules) + case result.Operation == reconciliation.ReconcileUpdate: + glog.Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules) + case result.Operation == reconciliation.ReconcileCreate: + glog.Infof("created role.%s/%s in %v ", rbac.GroupName, role.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 role.%s/%s in %v: %v", rbac.GroupName, role.Name, namespace, err)) + } + } + } + return true, nil }) // if we're never able to make it through intialization, kill the API server diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD index a3af80408ff..4c9524e6668 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD @@ -12,13 +12,16 @@ go_library( name = "go_default_library", srcs = [ "controller_policy.go", + "namespace_policy.go", "policy.go", ], tags = ["automanaged"], deps = [ "//pkg/apis/rbac:go_default_library", "//vendor:github.com/golang/glog", + "//vendor:k8s.io/apimachinery/pkg/api/meta", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", + "//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apiserver/pkg/authentication/user", ], ) diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go new file mode 100644 index 00000000000..35b9cae165c --- /dev/null +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/namespace_policy.go @@ -0,0 +1,65 @@ +/* +Copyright 2016 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 bootstrappolicy + +import ( + "strings" + + "github.com/golang/glog" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rbac "k8s.io/kubernetes/pkg/apis/rbac" +) + +var ( + // namespaceRoles is a map of namespace to slice of roles to create + namespaceRoles = map[string][]rbac.Role{} +) + +func addNamespaceRole(namespace string, role rbac.Role) { + if !strings.HasPrefix(namespace, "kube-") { + glog.Fatalf(`roles can only be bootstrapped into reserved namespaces starting with "kube-", not %q`, namespace) + } + + existingRoles := namespaceRoles[namespace] + for _, existingRole := range existingRoles { + if role.Name == existingRole.Name { + glog.Fatalf("role %q was already registered in %q", role.Name, namespace) + } + } + + role.Namespace = namespace + addDefaultMetadata(&role) + existingRoles = append(existingRoles, role) + namespaceRoles[namespace] = existingRoles +} + +func init() { + addNamespaceRole(metav1.NamespaceSystem, rbac.Role{ + // role for finding authentication config info for starting a server + ObjectMeta: metav1.ObjectMeta{Name: "extension-apiserver-authentication-reader"}, + Rules: []rbac.PolicyRule{ + // this particular config map is exposed and contains authentication configuration information + rbac.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names("extension-apiserver-authentication").RuleOrDie(), + }, + }) +} + +// NamespaceRoles returns a map of namespace to slice of roles to create +func NamespaceRoles() map[string][]rbac.Role { + return namespaceRoles +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go index f8b8cb0a989..d0e03e6ca0b 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go @@ -17,7 +17,9 @@ limitations under the License. package bootstrappolicy import ( + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authentication/user" rbac "k8s.io/kubernetes/pkg/apis/rbac" ) @@ -44,40 +46,42 @@ const ( storageGroup = "storage.k8s.io" ) +func addDefaultMetadata(obj runtime.Object) { + metadata, err := meta.Accessor(obj) + if err != nil { + // if this happens, then some static code is broken + panic(err) + } + + labels := metadata.GetLabels() + if labels == nil { + labels = map[string]string{} + } + for k, v := range Label { + labels[k] = v + } + metadata.SetLabels(labels) + + annotations := metadata.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + for k, v := range Annotation { + annotations[k] = v + } + metadata.SetAnnotations(annotations) +} + func addClusterRoleLabel(roles []rbac.ClusterRole) { for i := range roles { - if roles[i].ObjectMeta.Labels == nil { - roles[i].ObjectMeta.Labels = make(map[string]string) - } - 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 - } + addDefaultMetadata(&roles[i]) } return } func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) { for i := range rolebindings { - if rolebindings[i].ObjectMeta.Labels == nil { - rolebindings[i].ObjectMeta.Labels = make(map[string]string) - } - 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 - } + addDefaultMetadata(&rolebindings[i]) } return } diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go index 800a6884d75..22d0d2532ac 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go @@ -151,6 +151,28 @@ func TestEditViewRelationship(t *testing.T) { } } +func TestBootstrapNamespaceRoles(t *testing.T) { + list := &api.List{} + names := sets.NewString() + roles := map[string]runtime.Object{} + + namespaceRoles := bootstrappolicy.NamespaceRoles() + for _, namespace := range sets.StringKeySet(namespaceRoles).List() { + bootstrapRoles := namespaceRoles[namespace] + for i := range bootstrapRoles { + role := bootstrapRoles[i] + names.Insert(role.Name) + roles[role.Name] = &role + } + + for _, name := range names.List() { + list.Items = append(list.Items, roles[name]) + } + } + + testObjects(t, list, "namespace-roles.yaml") +} + func TestBootstrapClusterRoles(t *testing.T) { list := &api.List{} names := sets.NewString() diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml new file mode 100644 index 00000000000..6f78b9a0769 --- /dev/null +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml @@ -0,0 +1,23 @@ +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: extension-apiserver-authentication-reader + namespace: kube-system + rules: + - apiGroups: + - "" + resourceNames: + - extension-apiserver-authentication + resources: + - configmaps + verbs: + - get +kind: List +metadata: {}