diff --git a/pkg/registry/cachesize/cachesize.go b/pkg/registry/cachesize/cachesize.go index b07a4cdff4126..9bc360ff84f38 100644 --- a/pkg/registry/cachesize/cachesize.go +++ b/pkg/registry/cachesize/cachesize.go @@ -28,6 +28,8 @@ import ( type Resource string const ( + ClusterRoles Resource = "clusterroles" + ClusterRoleBindings Resource = "clusterrolebindings" Controllers Resource = "controllers" Daemonsets Resource = "daemonsets" Deployments Resource = "deployments" @@ -48,6 +50,8 @@ const ( Replicasets Resource = "replicasets" ResourceQuotas Resource = "resourcequotas" ScheduledJobs Resource = "scheduledjobs" + Roles Resource = "roles" + RoleBindings Resource = "rolebindings" Secrets Resource = "secrets" ServiceAccounts Resource = "serviceaccounts" Services Resource = "services" @@ -57,6 +61,8 @@ var watchCacheSizes map[Resource]int func init() { watchCacheSizes = make(map[Resource]int) + watchCacheSizes[ClusterRoles] = 100 + watchCacheSizes[ClusterRoleBindings] = 100 watchCacheSizes[Controllers] = 100 watchCacheSizes[Daemonsets] = 100 watchCacheSizes[Deployments] = 100 @@ -77,6 +83,8 @@ func init() { watchCacheSizes[Replicasets] = 100 watchCacheSizes[ResourceQuotas] = 100 watchCacheSizes[ScheduledJobs] = 100 + watchCacheSizes[Roles] = 100 + watchCacheSizes[RoleBindings] = 100 watchCacheSizes[Secrets] = 100 watchCacheSizes[ServiceAccounts] = 100 watchCacheSizes[Services] = 100 diff --git a/pkg/registry/clusterrole/doc.go b/pkg/registry/clusterrole/doc.go new file mode 100644 index 0000000000000..89c0edef4d3ae --- /dev/null +++ b/pkg/registry/clusterrole/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 certificates provides Registry interface and its RESTStorage +// implementation for storing ClusterRole objects. +package clusterrole diff --git a/pkg/registry/clusterrole/etcd/etcd.go b/pkg/registry/clusterrole/etcd/etcd.go new file mode 100644 index 0000000000000..e33ffaa6bb635 --- /dev/null +++ b/pkg/registry/clusterrole/etcd/etcd.go @@ -0,0 +1,76 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/clusterrole" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for ClusterRole against etcd +type REST struct { + *registry.Store +} + +// NewREST returns a RESTStorage object that will work against ClusterRole objects. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/clusterroles" + + newListFunc := func() runtime.Object { return &rbac.ClusterRoleList{} } + storageInterface := opts.Decorator( + opts.Storage, + cachesize.GetWatchCacheSizeByResource(cachesize.ClusterRoles), + &rbac.ClusterRole{}, + prefix, + clusterrole.Strategy, + newListFunc, + ) + + store := ®istry.Store{ + NewFunc: func() runtime.Object { return &rbac.ClusterRole{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return registry.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return registry.NoNamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*rbac.ClusterRole).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return clusterrole.Matcher(label, field) + }, + QualifiedResource: rbac.Resource("clusterroles"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: clusterrole.Strategy, + UpdateStrategy: clusterrole.Strategy, + DeleteStrategy: clusterrole.Strategy, + + Storage: storageInterface, + } + + return &REST{store} +} diff --git a/pkg/registry/clusterrole/policybased/storage.go b/pkg/registry/clusterrole/policybased/storage.go new file mode 100644 index 0000000000000..8e9f5cc9728f6 --- /dev/null +++ b/pkg/registry/clusterrole/policybased/storage.go @@ -0,0 +1,100 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 policybased implements a standard storage for ClusterRole that prevents privilege escalation. +package policybased + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/runtime" +) + +var groupResource = rbac.Resource("clusterroles") + +type Storage struct { + rest.StandardStorage + + ruleResolver validation.AuthorizationRuleResolver + + // user which skips privilege escalation checks + superUser string +} + +func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver, superUser string) *Storage { + return &Storage{s, ruleResolver, superUser} +} + +func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + if user, ok := api.UserFrom(ctx); ok { + if s.superUser != "" && user.GetName() == s.superUser { + return s.StandardStorage.Create(ctx, obj) + } + } + + clusterRole := obj.(*rbac.ClusterRole) + rules := clusterRole.Rules + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) + } + return s.StandardStorage.Create(ctx, obj) +} + +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) + } + } + + nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { + clusterRole := obj.(*rbac.ClusterRole) + + rules := clusterRole.Rules + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) + } + return obj, nil + }) + + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) +} + +// TODO(ericchiang): This logic is copied from #26240. Replace with once that PR is merged into master. +type wrappedUpdatedObjectInfo struct { + objInfo rest.UpdatedObjectInfo + + transformFunc rest.TransformFunc +} + +func wrapUpdatedObjectInfo(objInfo rest.UpdatedObjectInfo, transformFunc rest.TransformFunc) rest.UpdatedObjectInfo { + return &wrappedUpdatedObjectInfo{objInfo, transformFunc} +} + +func (i *wrappedUpdatedObjectInfo) Preconditions() *api.Preconditions { + return i.objInfo.Preconditions() +} + +func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx api.Context, oldObj runtime.Object) (runtime.Object, error) { + obj, err := i.objInfo.UpdatedObject(ctx, oldObj) + if err != nil { + return obj, err + } + return i.transformFunc(ctx, obj, oldObj) +} diff --git a/pkg/registry/clusterrole/registry.go b/pkg/registry/clusterrole/registry.go new file mode 100644 index 0000000000000..701fc55a0b989 --- /dev/null +++ b/pkg/registry/clusterrole/registry.go @@ -0,0 +1,81 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 clusterrole + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store ClusterRoles. +type Registry interface { + ListClusterRoles(ctx api.Context, options *api.ListOptions) (*rbac.ClusterRoleList, error) + CreateClusterRole(ctx api.Context, clusterRole *rbac.ClusterRole) error + UpdateClusterRole(ctx api.Context, clusterRole *rbac.ClusterRole) error + GetClusterRole(ctx api.Context, name string) (*rbac.ClusterRole, error) + DeleteClusterRole(ctx api.Context, name string) error + WatchClusterRoles(ctx api.Context, options *api.ListOptions) (watch.Interface, error) +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListClusterRoles(ctx api.Context, options *api.ListOptions) (*rbac.ClusterRoleList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + + return obj.(*rbac.ClusterRoleList), nil +} + +func (s *storage) CreateClusterRole(ctx api.Context, clusterRole *rbac.ClusterRole) error { + _, err := s.Create(ctx, clusterRole) + return err +} + +func (s *storage) UpdateClusterRole(ctx api.Context, clusterRole *rbac.ClusterRole) error { + _, _, err := s.Update(ctx, clusterRole.Name, rest.DefaultUpdatedObjectInfo(clusterRole, api.Scheme)) + return err +} + +func (s *storage) WatchClusterRoles(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetClusterRole(ctx api.Context, name string) (*rbac.ClusterRole, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*rbac.ClusterRole), nil +} + +func (s *storage) DeleteClusterRole(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/pkg/registry/clusterrole/strategy.go b/pkg/registry/clusterrole/strategy.go new file mode 100644 index 0000000000000..dc7e1e056549c --- /dev/null +++ b/pkg/registry/clusterrole/strategy.go @@ -0,0 +1,119 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 clusterrole + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for ClusterRoles +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// strategy is the default logic that applies when creating and updating +// ClusterRole objects. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +// Strategy should implement rest.RESTCreateStrategy +var _ rest.RESTCreateStrategy = Strategy + +// Strategy should implement rest.RESTUpdateStrategy +var _ rest.RESTUpdateStrategy = Strategy + +// NamespaceScoped is true for ClusterRoles. +func (strategy) NamespaceScoped() bool { + return false +} + +// AllowCreateOnUpdate is true for ClusterRoles. +func (strategy) AllowCreateOnUpdate() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users +// on creation. +func (strategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*rbac.ClusterRole) +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (strategy) PrepareForUpdate(obj, old runtime.Object) { + newClusterRole := obj.(*rbac.ClusterRole) + oldClusterRole := old.(*rbac.ClusterRole) + + _, _ = newClusterRole, oldClusterRole +} + +// Validate validates a new ClusterRole. Validation must check for a correct signature. +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + clusterRole := obj.(*rbac.ClusterRole) + return validation.ValidateClusterRole(clusterRole) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { + _ = obj.(*rbac.ClusterRole) +} + +// ValidateUpdate is the default update validation for an end user. +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + newObj := obj.(*rbac.ClusterRole) + errorList := validation.ValidateClusterRole(newObj) + return append(errorList, validation.ValidateClusterRoleUpdate(newObj, old.(*rbac.ClusterRole))...) +} + +// If AllowUnconditionalUpdate() is true and the object specified by +// the user does not have a resource version, then generic Update() +// populates it with the latest version. Else, it checks that the +// version specified by the user matches the version of latest etcd +// object. +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +func (s strategy) Export(obj runtime.Object, exact bool) error { + return nil +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*rbac.ClusterRole) + if !ok { + return false, fmt.Errorf("not a ClusterRole") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *rbac.ClusterRole) labels.Set { + return labels.Set{} +} diff --git a/pkg/registry/clusterrolebinding/doc.go b/pkg/registry/clusterrolebinding/doc.go new file mode 100644 index 0000000000000..8eb13084ad40a --- /dev/null +++ b/pkg/registry/clusterrolebinding/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 certificates provides Registry interface and its RESTStorage +// implementation for storing ClusterRoleBinding objects. +package clusterrolebinding diff --git a/pkg/registry/clusterrolebinding/etcd/etcd.go b/pkg/registry/clusterrolebinding/etcd/etcd.go new file mode 100644 index 0000000000000..c49b9b2dc867c --- /dev/null +++ b/pkg/registry/clusterrolebinding/etcd/etcd.go @@ -0,0 +1,76 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/clusterrolebinding" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for ClusterRoleBinding against etcd +type REST struct { + *registry.Store +} + +// NewREST returns a RESTStorage object that will work against ClusterRoleBinding objects. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/clusterrolebindings" + + newListFunc := func() runtime.Object { return &rbac.ClusterRoleBindingList{} } + storageInterface := opts.Decorator( + opts.Storage, + cachesize.GetWatchCacheSizeByResource(cachesize.ClusterRoleBindings), + &rbac.ClusterRoleBinding{}, + prefix, + clusterrolebinding.Strategy, + newListFunc, + ) + + store := ®istry.Store{ + NewFunc: func() runtime.Object { return &rbac.ClusterRoleBinding{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return registry.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return registry.NoNamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*rbac.ClusterRoleBinding).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return clusterrolebinding.Matcher(label, field) + }, + QualifiedResource: rbac.Resource("clusterrolebindings"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: clusterrolebinding.Strategy, + UpdateStrategy: clusterrolebinding.Strategy, + DeleteStrategy: clusterrolebinding.Strategy, + + Storage: storageInterface, + } + + return &REST{store} +} diff --git a/pkg/registry/clusterrolebinding/policybased/storage.go b/pkg/registry/clusterrolebinding/policybased/storage.go new file mode 100644 index 0000000000000..358a36bd8cf0c --- /dev/null +++ b/pkg/registry/clusterrolebinding/policybased/storage.go @@ -0,0 +1,106 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 policybased implements a standard storage for ClusterRoleBinding that prevents privilege escalation. +package policybased + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/runtime" +) + +var groupResource = rbac.Resource("clusterrolebindings") + +type Storage struct { + rest.StandardStorage + + ruleResolver validation.AuthorizationRuleResolver + + // user which skips privilege escalation checks + superUser string +} + +func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver, superUser string) *Storage { + return &Storage{s, ruleResolver, superUser} +} + +func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + if user, ok := api.UserFrom(ctx); ok { + if s.superUser != "" && user.GetName() == s.superUser { + return s.StandardStorage.Create(ctx, obj) + } + } + + clusterRoleBinding := obj.(*rbac.ClusterRoleBinding) + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, clusterRoleBinding.Namespace) + if err != nil { + return nil, err + } + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, clusterRoleBinding.Name, err) + } + return s.StandardStorage.Create(ctx, obj) +} + +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) + } + } + + nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { + clusterRoleBinding := obj.(*rbac.ClusterRoleBinding) + + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, clusterRoleBinding.Namespace) + if err != nil { + return nil, err + } + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, clusterRoleBinding.Name, err) + } + return obj, nil + }) + + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) +} + +// TODO(ericchiang): This logic is copied from #26240. Replace with once that PR is merged into master. +type wrappedUpdatedObjectInfo struct { + objInfo rest.UpdatedObjectInfo + + transformFunc rest.TransformFunc +} + +func wrapUpdatedObjectInfo(objInfo rest.UpdatedObjectInfo, transformFunc rest.TransformFunc) rest.UpdatedObjectInfo { + return &wrappedUpdatedObjectInfo{objInfo, transformFunc} +} + +func (i *wrappedUpdatedObjectInfo) Preconditions() *api.Preconditions { + return i.objInfo.Preconditions() +} + +func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx api.Context, oldObj runtime.Object) (runtime.Object, error) { + obj, err := i.objInfo.UpdatedObject(ctx, oldObj) + if err != nil { + return obj, err + } + return i.transformFunc(ctx, obj, oldObj) +} diff --git a/pkg/registry/clusterrolebinding/registry.go b/pkg/registry/clusterrolebinding/registry.go new file mode 100644 index 0000000000000..7f96eaa4a1427 --- /dev/null +++ b/pkg/registry/clusterrolebinding/registry.go @@ -0,0 +1,81 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 clusterrolebinding + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store ClusterRoleBindings. +type Registry interface { + ListClusterRoleBindings(ctx api.Context, options *api.ListOptions) (*rbac.ClusterRoleBindingList, error) + CreateClusterRoleBinding(ctx api.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error + UpdateClusterRoleBinding(ctx api.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error + GetClusterRoleBinding(ctx api.Context, name string) (*rbac.ClusterRoleBinding, error) + DeleteClusterRoleBinding(ctx api.Context, name string) error + WatchClusterRoleBindings(ctx api.Context, options *api.ListOptions) (watch.Interface, error) +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListClusterRoleBindings(ctx api.Context, options *api.ListOptions) (*rbac.ClusterRoleBindingList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + + return obj.(*rbac.ClusterRoleBindingList), nil +} + +func (s *storage) CreateClusterRoleBinding(ctx api.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error { + _, err := s.Create(ctx, clusterRoleBinding) + return err +} + +func (s *storage) UpdateClusterRoleBinding(ctx api.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error { + _, _, err := s.Update(ctx, clusterRoleBinding.Name, rest.DefaultUpdatedObjectInfo(clusterRoleBinding, api.Scheme)) + return err +} + +func (s *storage) WatchClusterRoleBindings(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetClusterRoleBinding(ctx api.Context, name string) (*rbac.ClusterRoleBinding, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*rbac.ClusterRoleBinding), nil +} + +func (s *storage) DeleteClusterRoleBinding(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/pkg/registry/clusterrolebinding/strategy.go b/pkg/registry/clusterrolebinding/strategy.go new file mode 100644 index 0000000000000..3dd1bf7e5976d --- /dev/null +++ b/pkg/registry/clusterrolebinding/strategy.go @@ -0,0 +1,119 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 clusterrolebinding + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for ClusterRoleBindings +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// strategy is the default logic that applies when creating and updating +// ClusterRoleBinding objects. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +// Strategy should implement rest.RESTCreateStrategy +var _ rest.RESTCreateStrategy = Strategy + +// Strategy should implement rest.RESTUpdateStrategy +var _ rest.RESTUpdateStrategy = Strategy + +// NamespaceScoped is true for ClusterRoleBindings. +func (strategy) NamespaceScoped() bool { + return false +} + +// AllowCreateOnUpdate is true for ClusterRoleBindings. +func (strategy) AllowCreateOnUpdate() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users +// on creation. +func (strategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*rbac.ClusterRoleBinding) +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (strategy) PrepareForUpdate(obj, old runtime.Object) { + newClusterRoleBinding := obj.(*rbac.ClusterRoleBinding) + oldClusterRoleBinding := old.(*rbac.ClusterRoleBinding) + + _, _ = newClusterRoleBinding, oldClusterRoleBinding +} + +// Validate validates a new ClusterRoleBinding. Validation must check for a correct signature. +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + clusterRoleBinding := obj.(*rbac.ClusterRoleBinding) + return validation.ValidateClusterRoleBinding(clusterRoleBinding) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { + _ = obj.(*rbac.ClusterRoleBinding) +} + +// ValidateUpdate is the default update validation for an end user. +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + newObj := obj.(*rbac.ClusterRoleBinding) + errorList := validation.ValidateClusterRoleBinding(newObj) + return append(errorList, validation.ValidateClusterRoleBindingUpdate(newObj, old.(*rbac.ClusterRoleBinding))...) +} + +// If AllowUnconditionalUpdate() is true and the object specified by +// the user does not have a resource version, then generic Update() +// populates it with the latest version. Else, it checks that the +// version specified by the user matches the version of latest etcd +// object. +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +func (s strategy) Export(obj runtime.Object, exact bool) error { + return nil +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*rbac.ClusterRoleBinding) + if !ok { + return false, fmt.Errorf("not a ClusterRoleBinding") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *rbac.ClusterRoleBinding) labels.Set { + return labels.Set{} +} diff --git a/pkg/registry/role/doc.go b/pkg/registry/role/doc.go new file mode 100644 index 0000000000000..2e867eb39e989 --- /dev/null +++ b/pkg/registry/role/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 certificates provides Registry interface and its RESTStorage +// implementation for storing Role objects. +package role diff --git a/pkg/registry/role/etcd/etcd.go b/pkg/registry/role/etcd/etcd.go new file mode 100644 index 0000000000000..0097292ea89ef --- /dev/null +++ b/pkg/registry/role/etcd/etcd.go @@ -0,0 +1,76 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/registry/role" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for Role against etcd +type REST struct { + *registry.Store +} + +// NewREST returns a RESTStorage object that will work against Role objects. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/roles" + + newListFunc := func() runtime.Object { return &rbac.RoleList{} } + storageInterface := opts.Decorator( + opts.Storage, + cachesize.GetWatchCacheSizeByResource(cachesize.Roles), + &rbac.Role{}, + prefix, + role.Strategy, + newListFunc, + ) + + store := ®istry.Store{ + NewFunc: func() runtime.Object { return &rbac.Role{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return registry.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return registry.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*rbac.Role).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return role.Matcher(label, field) + }, + QualifiedResource: rbac.Resource("roles"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: role.Strategy, + UpdateStrategy: role.Strategy, + DeleteStrategy: role.Strategy, + + Storage: storageInterface, + } + + return &REST{store} +} diff --git a/pkg/registry/role/policybased/storage.go b/pkg/registry/role/policybased/storage.go new file mode 100644 index 0000000000000..07ddc26a1629f --- /dev/null +++ b/pkg/registry/role/policybased/storage.go @@ -0,0 +1,100 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 policybased implements a standard storage for Role that prevents privilege escalation. +package policybased + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/runtime" +) + +var groupResource = rbac.Resource("roles") + +type Storage struct { + rest.StandardStorage + + ruleResolver validation.AuthorizationRuleResolver + + // user which skips privilege escalation checks + superUser string +} + +func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver, superUser string) *Storage { + return &Storage{s, ruleResolver, superUser} +} + +func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + if user, ok := api.UserFrom(ctx); ok { + if s.superUser != "" && user.GetName() == s.superUser { + return s.StandardStorage.Create(ctx, obj) + } + } + + role := obj.(*rbac.Role) + rules := role.Rules + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, role.Name, err) + } + return s.StandardStorage.Create(ctx, obj) +} + +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) + } + } + + nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { + role := obj.(*rbac.Role) + + rules := role.Rules + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, role.Name, err) + } + return obj, nil + }) + + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) +} + +// TODO(ericchiang): This logic is copied from #26240. Replace with once that PR is merged into master. +type wrappedUpdatedObjectInfo struct { + objInfo rest.UpdatedObjectInfo + + transformFunc rest.TransformFunc +} + +func wrapUpdatedObjectInfo(objInfo rest.UpdatedObjectInfo, transformFunc rest.TransformFunc) rest.UpdatedObjectInfo { + return &wrappedUpdatedObjectInfo{objInfo, transformFunc} +} + +func (i *wrappedUpdatedObjectInfo) Preconditions() *api.Preconditions { + return i.objInfo.Preconditions() +} + +func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx api.Context, oldObj runtime.Object) (runtime.Object, error) { + obj, err := i.objInfo.UpdatedObject(ctx, oldObj) + if err != nil { + return obj, err + } + return i.transformFunc(ctx, obj, oldObj) +} diff --git a/pkg/registry/role/registry.go b/pkg/registry/role/registry.go new file mode 100644 index 0000000000000..40f2975da84e2 --- /dev/null +++ b/pkg/registry/role/registry.go @@ -0,0 +1,81 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 role + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store Roles. +type Registry interface { + ListRoles(ctx api.Context, options *api.ListOptions) (*rbac.RoleList, error) + CreateRole(ctx api.Context, role *rbac.Role) error + UpdateRole(ctx api.Context, role *rbac.Role) error + GetRole(ctx api.Context, name string) (*rbac.Role, error) + DeleteRole(ctx api.Context, name string) error + WatchRoles(ctx api.Context, options *api.ListOptions) (watch.Interface, error) +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListRoles(ctx api.Context, options *api.ListOptions) (*rbac.RoleList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + + return obj.(*rbac.RoleList), nil +} + +func (s *storage) CreateRole(ctx api.Context, role *rbac.Role) error { + _, err := s.Create(ctx, role) + return err +} + +func (s *storage) UpdateRole(ctx api.Context, role *rbac.Role) error { + _, _, err := s.Update(ctx, role.Name, rest.DefaultUpdatedObjectInfo(role, api.Scheme)) + return err +} + +func (s *storage) WatchRoles(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetRole(ctx api.Context, name string) (*rbac.Role, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*rbac.Role), nil +} + +func (s *storage) DeleteRole(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/pkg/registry/role/strategy.go b/pkg/registry/role/strategy.go new file mode 100644 index 0000000000000..23efccd77fe06 --- /dev/null +++ b/pkg/registry/role/strategy.go @@ -0,0 +1,119 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 role + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for Roles +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// strategy is the default logic that applies when creating and updating +// Role objects. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +// Strategy should implement rest.RESTCreateStrategy +var _ rest.RESTCreateStrategy = Strategy + +// Strategy should implement rest.RESTUpdateStrategy +var _ rest.RESTUpdateStrategy = Strategy + +// NamespaceScoped is true for Roles. +func (strategy) NamespaceScoped() bool { + return true +} + +// AllowCreateOnUpdate is true for Roles. +func (strategy) AllowCreateOnUpdate() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users +// on creation. +func (strategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*rbac.Role) +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (strategy) PrepareForUpdate(obj, old runtime.Object) { + newRole := obj.(*rbac.Role) + oldRole := old.(*rbac.Role) + + _, _ = newRole, oldRole +} + +// Validate validates a new Role. Validation must check for a correct signature. +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + role := obj.(*rbac.Role) + return validation.ValidateRole(role) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { + _ = obj.(*rbac.Role) +} + +// ValidateUpdate is the default update validation for an end user. +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + newObj := obj.(*rbac.Role) + errorList := validation.ValidateRole(newObj) + return append(errorList, validation.ValidateRoleUpdate(newObj, old.(*rbac.Role))...) +} + +// If AllowUnconditionalUpdate() is true and the object specified by +// the user does not have a resource version, then generic Update() +// populates it with the latest version. Else, it checks that the +// version specified by the user matches the version of latest etcd +// object. +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +func (s strategy) Export(obj runtime.Object, exact bool) error { + return nil +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*rbac.Role) + if !ok { + return false, fmt.Errorf("not a Role") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *rbac.Role) labels.Set { + return labels.Set{} +} diff --git a/pkg/registry/rolebinding/doc.go b/pkg/registry/rolebinding/doc.go new file mode 100644 index 0000000000000..bac8e64fc079a --- /dev/null +++ b/pkg/registry/rolebinding/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 certificates provides Registry interface and its RESTStorage +// implementation for storing RoleBinding objects. +package rolebinding diff --git a/pkg/registry/rolebinding/etcd/etcd.go b/pkg/registry/rolebinding/etcd/etcd.go new file mode 100644 index 0000000000000..559a00402e0ea --- /dev/null +++ b/pkg/registry/rolebinding/etcd/etcd.go @@ -0,0 +1,76 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/registry/rolebinding" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for RoleBinding against etcd +type REST struct { + *registry.Store +} + +// NewREST returns a RESTStorage object that will work against RoleBinding objects. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/rolebindings" + + newListFunc := func() runtime.Object { return &rbac.RoleBindingList{} } + storageInterface := opts.Decorator( + opts.Storage, + cachesize.GetWatchCacheSizeByResource(cachesize.RoleBindings), + &rbac.RoleBinding{}, + prefix, + rolebinding.Strategy, + newListFunc, + ) + + store := ®istry.Store{ + NewFunc: func() runtime.Object { return &rbac.RoleBinding{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return registry.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return registry.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*rbac.RoleBinding).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return rolebinding.Matcher(label, field) + }, + QualifiedResource: rbac.Resource("rolebindings"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: rolebinding.Strategy, + UpdateStrategy: rolebinding.Strategy, + DeleteStrategy: rolebinding.Strategy, + + Storage: storageInterface, + } + + return &REST{store} +} diff --git a/pkg/registry/rolebinding/policybased/storage.go b/pkg/registry/rolebinding/policybased/storage.go new file mode 100644 index 0000000000000..df2049b5e1728 --- /dev/null +++ b/pkg/registry/rolebinding/policybased/storage.go @@ -0,0 +1,106 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 policybased implements a standard storage for RoleBinding that prevents privilege escalation. +package policybased + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/runtime" +) + +var groupResource = rbac.Resource("rolebindings") + +type Storage struct { + rest.StandardStorage + + ruleResolver validation.AuthorizationRuleResolver + + // user which skips privilege escalation checks + superUser string +} + +func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver, superUser string) *Storage { + return &Storage{s, ruleResolver, superUser} +} + +func (s *Storage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + if user, ok := api.UserFrom(ctx); ok { + if s.superUser != "" && user.GetName() == s.superUser { + return s.StandardStorage.Create(ctx, obj) + } + } + + roleBinding := obj.(*rbac.RoleBinding) + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, roleBinding.RoleRef, roleBinding.Namespace) + if err != nil { + return nil, err + } + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, roleBinding.Name, err) + } + return s.StandardStorage.Create(ctx, obj) +} + +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) + } + } + + nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { + roleBinding := obj.(*rbac.RoleBinding) + + rules, err := s.ruleResolver.GetRoleReferenceRules(ctx, roleBinding.RoleRef, roleBinding.Namespace) + if err != nil { + return nil, err + } + if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { + return nil, errors.NewForbidden(groupResource, roleBinding.Name, err) + } + return obj, nil + }) + + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) +} + +// TODO(ericchiang): This logic is copied from #26240. Replace with once that PR is merged into master. +type wrappedUpdatedObjectInfo struct { + objInfo rest.UpdatedObjectInfo + + transformFunc rest.TransformFunc +} + +func wrapUpdatedObjectInfo(objInfo rest.UpdatedObjectInfo, transformFunc rest.TransformFunc) rest.UpdatedObjectInfo { + return &wrappedUpdatedObjectInfo{objInfo, transformFunc} +} + +func (i *wrappedUpdatedObjectInfo) Preconditions() *api.Preconditions { + return i.objInfo.Preconditions() +} + +func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx api.Context, oldObj runtime.Object) (runtime.Object, error) { + obj, err := i.objInfo.UpdatedObject(ctx, oldObj) + if err != nil { + return obj, err + } + return i.transformFunc(ctx, obj, oldObj) +} diff --git a/pkg/registry/rolebinding/registry.go b/pkg/registry/rolebinding/registry.go new file mode 100644 index 0000000000000..435a2d57f7554 --- /dev/null +++ b/pkg/registry/rolebinding/registry.go @@ -0,0 +1,82 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 rolebinding + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store RoleBindings. +type Registry interface { + ListRoleBindings(ctx api.Context, options *api.ListOptions) (*rbac.RoleBindingList, error) + CreateRoleBinding(ctx api.Context, roleBinding *rbac.RoleBinding) error + UpdateRoleBinding(ctx api.Context, roleBinding *rbac.RoleBinding) error + GetRoleBinding(ctx api.Context, name string) (*rbac.RoleBinding, error) + DeleteRoleBinding(ctx api.Context, name string) error + WatchRoleBindings(ctx api.Context, options *api.ListOptions) (watch.Interface, error) +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListRoleBindings(ctx api.Context, options *api.ListOptions) (*rbac.RoleBindingList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + + return obj.(*rbac.RoleBindingList), nil +} + +func (s *storage) CreateRoleBinding(ctx api.Context, roleBinding *rbac.RoleBinding) error { + // TODO(ericchiang): add additional validation + _, err := s.Create(ctx, roleBinding) + return err +} + +func (s *storage) UpdateRoleBinding(ctx api.Context, roleBinding *rbac.RoleBinding) error { + _, _, err := s.Update(ctx, roleBinding.Name, rest.DefaultUpdatedObjectInfo(roleBinding, api.Scheme)) + return err +} + +func (s *storage) WatchRoleBindings(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetRoleBinding(ctx api.Context, name string) (*rbac.RoleBinding, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*rbac.RoleBinding), nil +} + +func (s *storage) DeleteRoleBinding(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/pkg/registry/rolebinding/strategy.go b/pkg/registry/rolebinding/strategy.go new file mode 100644 index 0000000000000..7818025a75974 --- /dev/null +++ b/pkg/registry/rolebinding/strategy.go @@ -0,0 +1,119 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 rolebinding + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apis/rbac/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for RoleBindings +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// strategy is the default logic that applies when creating and updating +// RoleBinding objects. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +// Strategy should implement rest.RESTCreateStrategy +var _ rest.RESTCreateStrategy = Strategy + +// Strategy should implement rest.RESTUpdateStrategy +var _ rest.RESTUpdateStrategy = Strategy + +// NamespaceScoped is true for RoleBindings. +func (strategy) NamespaceScoped() bool { + return true +} + +// AllowCreateOnUpdate is true for RoleBindings. +func (strategy) AllowCreateOnUpdate() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users +// on creation. +func (strategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*rbac.RoleBinding) +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (strategy) PrepareForUpdate(obj, old runtime.Object) { + newRoleBinding := obj.(*rbac.RoleBinding) + oldRoleBinding := old.(*rbac.RoleBinding) + + _, _ = newRoleBinding, oldRoleBinding +} + +// Validate validates a new RoleBinding. Validation must check for a correct signature. +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + roleBinding := obj.(*rbac.RoleBinding) + return validation.ValidateRoleBinding(roleBinding) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { + _ = obj.(*rbac.RoleBinding) +} + +// ValidateUpdate is the default update validation for an end user. +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + newObj := obj.(*rbac.RoleBinding) + errorList := validation.ValidateRoleBinding(newObj) + return append(errorList, validation.ValidateRoleBindingUpdate(newObj, old.(*rbac.RoleBinding))...) +} + +// If AllowUnconditionalUpdate() is true and the object specified by +// the user does not have a resource version, then generic Update() +// populates it with the latest version. Else, it checks that the +// version specified by the user matches the version of latest etcd +// object. +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +func (s strategy) Export(obj runtime.Object, exact bool) error { + return nil +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*rbac.RoleBinding) + if !ok { + return false, fmt.Errorf("not a RoleBinding") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *rbac.RoleBinding) labels.Set { + return labels.Set{} +}