mirror of https://github.com/k3s-io/k3s
pkg/registry: add rbac storage
parent
e3604e2590
commit
6f312f145d
|
@ -28,6 +28,8 @@ import (
|
||||||
type Resource string
|
type Resource string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ClusterRoles Resource = "clusterroles"
|
||||||
|
ClusterRoleBindings Resource = "clusterrolebindings"
|
||||||
Controllers Resource = "controllers"
|
Controllers Resource = "controllers"
|
||||||
Daemonsets Resource = "daemonsets"
|
Daemonsets Resource = "daemonsets"
|
||||||
Deployments Resource = "deployments"
|
Deployments Resource = "deployments"
|
||||||
|
@ -48,6 +50,8 @@ const (
|
||||||
Replicasets Resource = "replicasets"
|
Replicasets Resource = "replicasets"
|
||||||
ResourceQuotas Resource = "resourcequotas"
|
ResourceQuotas Resource = "resourcequotas"
|
||||||
ScheduledJobs Resource = "scheduledjobs"
|
ScheduledJobs Resource = "scheduledjobs"
|
||||||
|
Roles Resource = "roles"
|
||||||
|
RoleBindings Resource = "rolebindings"
|
||||||
Secrets Resource = "secrets"
|
Secrets Resource = "secrets"
|
||||||
ServiceAccounts Resource = "serviceaccounts"
|
ServiceAccounts Resource = "serviceaccounts"
|
||||||
Services Resource = "services"
|
Services Resource = "services"
|
||||||
|
@ -57,6 +61,8 @@ var watchCacheSizes map[Resource]int
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
watchCacheSizes = make(map[Resource]int)
|
watchCacheSizes = make(map[Resource]int)
|
||||||
|
watchCacheSizes[ClusterRoles] = 100
|
||||||
|
watchCacheSizes[ClusterRoleBindings] = 100
|
||||||
watchCacheSizes[Controllers] = 100
|
watchCacheSizes[Controllers] = 100
|
||||||
watchCacheSizes[Daemonsets] = 100
|
watchCacheSizes[Daemonsets] = 100
|
||||||
watchCacheSizes[Deployments] = 100
|
watchCacheSizes[Deployments] = 100
|
||||||
|
@ -77,6 +83,8 @@ func init() {
|
||||||
watchCacheSizes[Replicasets] = 100
|
watchCacheSizes[Replicasets] = 100
|
||||||
watchCacheSizes[ResourceQuotas] = 100
|
watchCacheSizes[ResourceQuotas] = 100
|
||||||
watchCacheSizes[ScheduledJobs] = 100
|
watchCacheSizes[ScheduledJobs] = 100
|
||||||
|
watchCacheSizes[Roles] = 100
|
||||||
|
watchCacheSizes[RoleBindings] = 100
|
||||||
watchCacheSizes[Secrets] = 100
|
watchCacheSizes[Secrets] = 100
|
||||||
watchCacheSizes[ServiceAccounts] = 100
|
watchCacheSizes[ServiceAccounts] = 100
|
||||||
watchCacheSizes[Services] = 100
|
watchCacheSizes[Services] = 100
|
||||||
|
|
|
@ -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
|
|
@ -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}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{}
|
||||||
|
}
|
|
@ -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
|
|
@ -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}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{}
|
||||||
|
}
|
|
@ -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
|
|
@ -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}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{}
|
||||||
|
}
|
|
@ -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
|
|
@ -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}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{}
|
||||||
|
}
|
Loading…
Reference in New Issue