From 467b7d928f4c645cc1f2b79ec366dc796a238fd1 Mon Sep 17 00:00:00 2001 From: deads2k Date: Tue, 4 Oct 2016 10:34:01 -0400 Subject: [PATCH 1/2] add clusterrolebindings to bootstrapping --- pkg/apis/rbac/helpers.go | 59 +++++++++++++++++++ pkg/generated/openapi/zz_generated.openapi.go | 17 ++++++ pkg/registry/rbac/rest/storage_rbac.go | 20 +++++++ .../rbac/bootstrappolicy/controller_policy.go | 17 +++++- .../authorizer/rbac/bootstrappolicy/policy.go | 9 +++ 5 files changed, 119 insertions(+), 3 deletions(-) diff --git a/pkg/apis/rbac/helpers.go b/pkg/apis/rbac/helpers.go index 0ae692bfb80..93ed6f99546 100644 --- a/pkg/apis/rbac/helpers.go +++ b/pkg/apis/rbac/helpers.go @@ -20,6 +20,7 @@ import ( "fmt" "strings" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" ) @@ -162,3 +163,61 @@ func (r *PolicyRuleBuilder) Rule() (PolicyRule, error) { return r.PolicyRule, nil } + +// +k8s:deepcopy-gen=false +// ClusterRoleBindingBuilder let's us attach methods. A no-no for API types. +// We use it to construct bindings in code. It's more compact than trying to write them +// out in a literal. +type ClusterRoleBindingBuilder struct { + ClusterRoleBinding ClusterRoleBinding +} + +func NewClusterBinding(clusterRoleName string) *ClusterRoleBindingBuilder { + return &ClusterRoleBindingBuilder{ + ClusterRoleBinding: ClusterRoleBinding{ + ObjectMeta: api.ObjectMeta{Name: clusterRoleName}, + RoleRef: RoleRef{ + APIGroup: GroupName, + Kind: "ClusterRole", + Name: clusterRoleName, + }, + }, + } +} + +func (r *ClusterRoleBindingBuilder) Groups(groups ...string) *ClusterRoleBindingBuilder { + for _, group := range groups { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: GroupKind, Name: group}) + } + return r +} + +func (r *ClusterRoleBindingBuilder) Users(users ...string) *ClusterRoleBindingBuilder { + for _, user := range users { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: UserKind, Name: user}) + } + return r +} + +func (r *ClusterRoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *ClusterRoleBindingBuilder { + for _, saName := range serviceAccountNames { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName}) + } + return r +} + +func (r *ClusterRoleBindingBuilder) BindingOrDie() ClusterRoleBinding { + ret, err := r.Binding() + if err != nil { + panic(err) + } + return ret +} + +func (r *ClusterRoleBindingBuilder) Binding() (ClusterRoleBinding, error) { + if len(r.ClusterRoleBinding.Subjects) == 0 { + return ClusterRoleBinding{}, fmt.Errorf("subjects are required: %#v", r.ClusterRoleBinding) + } + + return r.ClusterRoleBinding, nil +} diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index dfa85bdbd81..9b729ddc63c 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -5004,6 +5004,23 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{ Dependencies: []string{ "api.ObjectMeta", "rbac.RoleRef", "rbac.Subject", "unversioned.TypeMeta"}, }, + "rbac.ClusterRoleBindingBuilder": { + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterRoleBindingBuilder let's us attach methods. A no-no for API types. We use it to construct bindings in code. It's more compact than trying to write them out in a literal.", + Properties: map[string]spec.Schema{ + "ClusterRoleBinding": { + SchemaProps: spec.SchemaProps{ + Ref: spec.MustCreateRef("#/definitions/rbac.ClusterRoleBinding"), + }, + }, + }, + Required: []string{"ClusterRoleBinding"}, + }, + }, + Dependencies: []string{ + "rbac.ClusterRoleBinding"}, + }, "rbac.ClusterRoleBindingList": { Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ diff --git a/pkg/registry/rbac/rest/storage_rbac.go b/pkg/registry/rbac/rest/storage_rbac.go index f77d7bc2b37..0f34400c645 100644 --- a/pkg/registry/rbac/rest/storage_rbac.go +++ b/pkg/registry/rbac/rest/storage_rbac.go @@ -131,5 +131,25 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { glog.Infof("Created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name) } + existingClusterRoleBindings, err := clientset.ClusterRoleBindings().List(api.ListOptions{}) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to initialize clusterrolebindings: %v", err)) + return nil + } + // if clusterrolebindings already exist, then assume we don't have work to do because we've already + // initialized or another API server has started this task + if len(existingClusterRoleBindings.Items) > 0 { + return nil + } + + 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 + } + glog.Infof("Created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name) + } + return nil } diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go index e00854f97ba..b92483e5a04 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go @@ -25,14 +25,18 @@ import ( rbac "k8s.io/kubernetes/pkg/apis/rbac" ) +const saRolePrefix = "system:controller:" + var ( // controllerRoles is a slice of roles used for controllers controllerRoles = []rbac.ClusterRole{} + // controllerRoleBindings is a slice of roles used for controllers + controllerRoleBindings = []rbac.ClusterRoleBinding{} ) func addControllerRole(role rbac.ClusterRole) { - if !strings.HasPrefix(role.Name, "system:controller:") { - glog.Fatalf(`role %q must start with "system:controller:"`, role.Name) + if !strings.HasPrefix(role.Name, saRolePrefix) { + glog.Fatalf(`role %q must start with %q`, role.Name, saRolePrefix) } for _, existingRole := range controllerRoles { @@ -42,6 +46,8 @@ func addControllerRole(role rbac.ClusterRole) { } controllerRoles = append(controllerRoles, role) + controllerRoleBindings = append(controllerRoleBindings, + rbac.NewClusterBinding(role.Name).SAs("kube-system", role.Name[len(saRolePrefix):]).BindingOrDie()) } func eventsRule() rbac.PolicyRule { @@ -50,7 +56,7 @@ func eventsRule() rbac.PolicyRule { func init() { addControllerRole(rbac.ClusterRole{ - ObjectMeta: api.ObjectMeta{Name: "system:controller:replication-controller"}, + ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "replication-controller"}, Rules: []rbac.PolicyRule{ rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("replicationcontrollers").RuleOrDie(), rbac.NewRule("update").Groups(legacyGroup).Resources("replicationcontrollers/status").RuleOrDie(), @@ -64,3 +70,8 @@ func init() { func ControllerRoles() []rbac.ClusterRole { return controllerRoles } + +// ControllerRoleBindings returns the role bindings used by controllers +func ControllerRoleBindings() []rbac.ClusterRoleBinding { + return controllerRoleBindings +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go index 592b5f8668f..a8b3084bb44 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go @@ -19,6 +19,7 @@ package bootstrappolicy import ( "k8s.io/kubernetes/pkg/api" rbac "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/auth/user" ) var ( @@ -48,3 +49,11 @@ func ClusterRoles() []rbac.ClusterRole { }, } } + +// ClusterRoleBindings return default rolebindings to the default roles +func ClusterRoleBindings() []rbac.ClusterRoleBinding { + return []rbac.ClusterRoleBinding{ + rbac.NewClusterBinding("cluster-admin").Groups(user.SystemPrivilegedGroup).BindingOrDie(), + rbac.NewClusterBinding("system:discovery").Groups(user.AllAuthenticated, user.AllUnauthenticated).BindingOrDie(), + } +} From f73d1ea90a95dac4704a61fe80c647f8f9d47f6b Mon Sep 17 00:00:00 2001 From: deads2k Date: Tue, 4 Oct 2016 11:07:42 -0400 Subject: [PATCH 2/2] make sure that the bootstrap rbac rules are getting created in test-cmd --- hack/make-rules/test-cmd.sh | 4 ++ .../rbac/clusterrole/policybased/storage.go | 22 +++------ .../clusterrolebinding/policybased/storage.go | 22 +++------ pkg/registry/rbac/escalation_check.go | 45 +++++++++++++++++++ pkg/registry/rbac/role/policybased/storage.go | 22 +++------ .../rbac/rolebinding/policybased/storage.go | 22 +++------ 6 files changed, 69 insertions(+), 68 deletions(-) create mode 100644 pkg/registry/rbac/escalation_check.go diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index edd3cd4a4b3..6ab1e26edf7 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -419,6 +419,10 @@ runTests() { kubectl get "${kube_flags[@]}" --raw /version + # make sure the server was properly bootstrapped with clusterroles and bindings + kube::test::get_object_assert clusterroles/cluster-admin "{{.metadata.name}}" 'cluster-admin' + kube::test::get_object_assert clusterrolebindings/cluster-admin "{{.metadata.name}}" 'cluster-admin' + ########################### # POD creation / deletion # ########################### diff --git a/pkg/registry/rbac/clusterrole/policybased/storage.go b/pkg/registry/rbac/clusterrole/policybased/storage.go index a605ce74517..a4909a7da0a 100644 --- a/pkg/registry/rbac/clusterrole/policybased/storage.go +++ b/pkg/registry/rbac/clusterrole/policybased/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" - "k8s.io/kubernetes/pkg/auth/user" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/pkg/runtime" ) @@ -43,18 +43,8 @@ func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRul } func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { - if u, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && u.GetName() == s.superUser { - return s.StandardStorage.Create(ctx, obj) - } - - // system:masters is special because the API server uses it for privileged loopback connections - // therefore we know that a member of system:masters can always do anything - for _, group := range u.GetGroups() { - if group == user.SystemPrivilegedGroup { - return s.StandardStorage.Create(ctx, obj) - } - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Create(ctx, obj) } clusterRole := obj.(*rbac.ClusterRole) @@ -66,10 +56,8 @@ func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, e } func (s *Storage) Update(ctx api.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - if user, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && user.GetName() == s.superUser { - return s.StandardStorage.Update(ctx, name, obj) - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Update(ctx, name, obj) } nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { diff --git a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go index 539e97d1250..39fdc7d8267 100644 --- a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go +++ b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" - "k8s.io/kubernetes/pkg/auth/user" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/pkg/runtime" ) @@ -43,18 +43,8 @@ func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRul } func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { - if u, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && u.GetName() == s.superUser { - return s.StandardStorage.Create(ctx, obj) - } - - // system:masters is special because the API server uses it for privileged loopback connections - // therefore we know that a member of system:masters can always do anything - for _, group := range u.GetGroups() { - if group == user.SystemPrivilegedGroup { - return s.StandardStorage.Create(ctx, obj) - } - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Create(ctx, obj) } clusterRoleBinding := obj.(*rbac.ClusterRoleBinding) @@ -69,10 +59,8 @@ func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, e } func (s *Storage) Update(ctx api.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - if user, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && user.GetName() == s.superUser { - return s.StandardStorage.Update(ctx, name, obj) - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Update(ctx, name, obj) } nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { diff --git a/pkg/registry/rbac/escalation_check.go b/pkg/registry/rbac/escalation_check.go new file mode 100644 index 00000000000..c5ba65a1f4e --- /dev/null +++ b/pkg/registry/rbac/escalation_check.go @@ -0,0 +1,45 @@ +/* +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 rbac + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/auth/user" +) + +func EscalationAllowed(ctx api.Context, superUser string) bool { + u, ok := api.UserFrom(ctx) + if !ok { + // the only way to be without a user is to either have no authenticators by explicitly saying that's your preference + // or to be connecting via the insecure port, in which case this logically doesn't apply + return true + } + + // check to see if this subject is allowed to escalate + if len(superUser) != 0 && u.GetName() == superUser { + return true + } + // system:masters is special because the API server uses it for privileged loopback connections + // therefore we know that a member of system:masters can always do anything + for _, group := range u.GetGroups() { + if group == user.SystemPrivilegedGroup { + return true + } + } + + return false +} diff --git a/pkg/registry/rbac/role/policybased/storage.go b/pkg/registry/rbac/role/policybased/storage.go index 38ad81decfd..c7976b9defa 100644 --- a/pkg/registry/rbac/role/policybased/storage.go +++ b/pkg/registry/rbac/role/policybased/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" - "k8s.io/kubernetes/pkg/auth/user" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/pkg/runtime" ) @@ -43,18 +43,8 @@ func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRul } func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { - if u, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && u.GetName() == s.superUser { - return s.StandardStorage.Create(ctx, obj) - } - - // system:masters is special because the API server uses it for privileged loopback connections - // therefore we know that a member of system:masters can always do anything - for _, group := range u.GetGroups() { - if group == user.SystemPrivilegedGroup { - return s.StandardStorage.Create(ctx, obj) - } - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Create(ctx, obj) } role := obj.(*rbac.Role) @@ -66,10 +56,8 @@ func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, e } func (s *Storage) Update(ctx api.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - if user, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && user.GetName() == s.superUser { - return s.StandardStorage.Update(ctx, name, obj) - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Update(ctx, name, obj) } nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { diff --git a/pkg/registry/rbac/rolebinding/policybased/storage.go b/pkg/registry/rbac/rolebinding/policybased/storage.go index 49750d457dc..8fb2c435020 100644 --- a/pkg/registry/rbac/rolebinding/policybased/storage.go +++ b/pkg/registry/rbac/rolebinding/policybased/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" - "k8s.io/kubernetes/pkg/auth/user" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/pkg/runtime" ) @@ -43,18 +43,8 @@ func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRul } func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { - if u, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && u.GetName() == s.superUser { - return s.StandardStorage.Create(ctx, obj) - } - - // system:masters is special because the API server uses it for privileged loopback connections - // therefore we know that a member of system:masters can always do anything - for _, group := range u.GetGroups() { - if group == user.SystemPrivilegedGroup { - return s.StandardStorage.Create(ctx, obj) - } - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Create(ctx, obj) } roleBinding := obj.(*rbac.RoleBinding) @@ -69,10 +59,8 @@ func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, e } func (s *Storage) Update(ctx api.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - if user, ok := api.UserFrom(ctx); ok { - if s.superUser != "" && user.GetName() == s.superUser { - return s.StandardStorage.Update(ctx, name, obj) - } + if rbacregistry.EscalationAllowed(ctx, s.superUser) { + return s.StandardStorage.Update(ctx, name, obj) } nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {