vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

61
vendor/k8s.io/kubernetes/pkg/registry/rbac/BUILD generated vendored Normal file
View File

@ -0,0 +1,61 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"escalation_check.go",
"helpers.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac",
deps = [
"//pkg/apis/rbac:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/registry/rbac/clusterrole:all-srcs",
"//pkg/registry/rbac/clusterrolebinding:all-srcs",
"//pkg/registry/rbac/reconciliation:all-srcs",
"//pkg/registry/rbac/rest:all-srcs",
"//pkg/registry/rbac/role:all-srcs",
"//pkg/registry/rbac/rolebinding:all-srcs",
"//pkg/registry/rbac/validation:all-srcs",
],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["helpers_test.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac",
library = ":go_default_library",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)

4
vendor/k8s.io/kubernetes/pkg/registry/rbac/OWNERS generated vendored Executable file
View File

@ -0,0 +1,4 @@
reviewers:
- deads2k
- liggitt
- enj

View File

@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"registry.go",
"strategy.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrole",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/registry/rbac/clusterrole/policybased:all-srcs",
"//pkg/registry/rbac/clusterrole/storage:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,19 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package certificates provides Registry interface and its RESTStorage
// implementation for storing ClusterRole objects.
package clusterrole // import "k8s.io/kubernetes/pkg/registry/rbac/clusterrole"

View File

@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/policybased",
deps = [
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,103 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package policybased implements a standard storage for ClusterRole that prevents privilege escalation.
package policybased
import (
"errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("clusterroles")
type Storage struct {
rest.StandardStorage
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, ruleResolver}
}
var fullAuthority = []rbac.PolicyRule{
rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(),
rbac.NewRule("*").URLs("*").RuleOrDie(),
}
func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Create(ctx, obj, createValidatingAdmission, includeUninitialized)
}
clusterRole := obj.(*rbac.ClusterRole)
rules := clusterRole.Rules
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, err)
}
// to set the aggregation rule, since it can gather anything, requires * on *.*
if hasAggregationRule(clusterRole) {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, fullAuthority); err != nil {
return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, errors.New("must have cluster-admin privileges to use the aggregationRule"))
}
}
return s.StandardStorage.Create(ctx, obj, createValidatingAdmission, includeUninitialized)
}
func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation)
}
nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {
clusterRole := obj.(*rbac.ClusterRole)
oldClusterRole := oldObj.(*rbac.ClusterRole)
// if we're only mutating fields needed for the GC to eventually delete this obj, return
if rbacregistry.IsOnlyMutatingGCFields(obj, oldObj, kapihelper.Semantic) {
return obj, nil
}
rules := clusterRole.Rules
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, err)
}
// to change the aggregation rule, since it can gather anything and prevent tightening, requires * on *.*
if hasAggregationRule(clusterRole) || hasAggregationRule(oldClusterRole) {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, fullAuthority); err != nil {
return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, errors.New("must have cluster-admin privileges to use the aggregationRule"))
}
}
return obj, nil
})
return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation)
}
func hasAggregationRule(clusterRole *rbac.ClusterRole) bool {
return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0
}

View File

@ -0,0 +1,92 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clusterrole
import (
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// Registry is an interface for things that know how to store ClusterRoles.
type Registry interface {
ListClusterRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.ClusterRoleList, error)
CreateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc) error
UpdateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error
GetClusterRole(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.ClusterRole, error)
DeleteClusterRole(ctx genericapirequest.Context, name string) error
WatchClusterRoles(ctx genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc) error {
_, err := s.Create(ctx, clusterRole, createValidation, false)
return err
}
func (s *storage) UpdateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error {
_, _, err := s.Update(ctx, clusterRole.Name, rest.DefaultUpdatedObjectInfo(clusterRole), createValidation, updateValidation)
return err
}
func (s *storage) WatchClusterRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
return s.Watch(ctx, options)
}
func (s *storage) GetClusterRole(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.ClusterRole, error) {
obj, err := s.Get(ctx, name, options)
if err != nil {
return nil, err
}
return obj.(*rbac.ClusterRole), nil
}
func (s *storage) DeleteClusterRole(ctx genericapirequest.Context, name string) error {
_, _, err := s.Delete(ctx, name, nil)
return err
}
// AuthorizerAdapter adapts the registry to the authorizer interface
type AuthorizerAdapter struct {
Registry Registry
}
func (a AuthorizerAdapter) GetClusterRole(name string) (*rbac.ClusterRole, error) {
return a.Registry.GetClusterRole(genericapirequest.NewContext(), name, &metav1.GetOptions{})
}

View File

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/storage",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac/clusterrole:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,49 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storage
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/registry/rbac/clusterrole"
)
// REST implements a RESTStorage for ClusterRole
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against ClusterRole objects.
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &rbac.ClusterRole{} },
NewListFunc: func() runtime.Object { return &rbac.ClusterRoleList{} },
DefaultQualifiedResource: rbac.Resource("clusterroles"),
CreateStrategy: clusterrole.Strategy,
UpdateStrategy: clusterrole.Strategy,
DeleteStrategy: clusterrole.Strategy,
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil {
panic(err) // TODO: Propagate error up
}
return &REST{store}
}

View File

@ -0,0 +1,95 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clusterrole
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
)
// strategy implements behavior for ClusterRoles
type strategy struct {
runtime.ObjectTyper
names.NameGenerator
}
// strategy is the default logic that applies when creating and updating
// ClusterRole objects.
var Strategy = strategy{legacyscheme.Scheme, names.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(ctx genericapirequest.Context, obj runtime.Object) {
_ = obj.(*rbac.ClusterRole)
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (strategy) PrepareForUpdate(ctx genericapirequest.Context, 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 genericapirequest.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 genericapirequest.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
}

View File

@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"registry.go",
"strategy.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/registry/rbac/clusterrolebinding/policybased:all-srcs",
"//pkg/registry/rbac/clusterrolebinding/storage:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,19 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package certificates provides Registry interface and its RESTStorage
// implementation for storing ClusterRoleBinding objects.
package clusterrolebinding // import "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"

View File

@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/policybased",
deps = [
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,97 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package policybased implements a standard storage for ClusterRoleBinding that prevents privilege escalation.
package policybased
import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("clusterrolebindings")
type Storage struct {
rest.StandardStorage
authorizer authorizer.Authorizer
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, authorizer, ruleResolver}
}
func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
clusterRoleBinding := obj.(*rbac.ClusterRoleBinding)
if rbacregistry.BindingAuthorized(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone, s.authorizer) {
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
rules, err := s.ruleResolver.GetRoleReferenceRules(clusterRoleBinding.RoleRef, metav1.NamespaceNone)
if err != nil {
return nil, err
}
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, clusterRoleBinding.Name, err)
}
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation)
}
nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {
clusterRoleBinding := obj.(*rbac.ClusterRoleBinding)
// if we're only mutating fields needed for the GC to eventually delete this obj, return
if rbacregistry.IsOnlyMutatingGCFields(obj, oldObj, kapihelper.Semantic) {
return obj, nil
}
// if we're explicitly authorized to bind this clusterrole, return
if rbacregistry.BindingAuthorized(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone, s.authorizer) {
return obj, nil
}
// Otherwise, see if we already have all the permissions contained in the referenced clusterrole
rules, err := s.ruleResolver.GetRoleReferenceRules(clusterRoleBinding.RoleRef, metav1.NamespaceNone)
if err != nil {
return nil, err
}
if err := rbacregistryvalidation.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, createValidation, updateValidation)
}

View File

@ -0,0 +1,101 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clusterrolebinding
import (
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// Registry is an interface for things that know how to store ClusterRoleBindings.
type Registry interface {
ListClusterRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.ClusterRoleBindingList, error)
CreateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc) error
UpdateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error
GetClusterRoleBinding(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.ClusterRoleBinding, error)
DeleteClusterRoleBinding(ctx genericapirequest.Context, name string) error
WatchClusterRoleBindings(ctx genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc) error {
_, err := s.Create(ctx, clusterRoleBinding, createValidation, false)
return err
}
func (s *storage) UpdateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error {
_, _, err := s.Update(ctx, clusterRoleBinding.Name, rest.DefaultUpdatedObjectInfo(clusterRoleBinding), createValidation, updateValidation)
return err
}
func (s *storage) WatchClusterRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
return s.Watch(ctx, options)
}
func (s *storage) GetClusterRoleBinding(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.ClusterRoleBinding, error) {
obj, err := s.Get(ctx, name, options)
if err != nil {
return nil, err
}
return obj.(*rbac.ClusterRoleBinding), nil
}
func (s *storage) DeleteClusterRoleBinding(ctx genericapirequest.Context, name string) error {
_, _, err := s.Delete(ctx, name, nil)
return err
}
// AuthorizerAdapter adapts the registry to the authorizer interface
type AuthorizerAdapter struct {
Registry Registry
}
func (a AuthorizerAdapter) ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error) {
list, err := a.Registry.ListClusterRoleBindings(genericapirequest.NewContext(), &metainternalversion.ListOptions{})
if err != nil {
return nil, err
}
ret := []*rbac.ClusterRoleBinding{}
for i := range list.Items {
ret = append(ret, &list.Items[i])
}
return ret, nil
}

View File

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/storage",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac/clusterrolebinding:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,49 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storage
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"
)
// REST implements a RESTStorage for ClusterRoleBinding
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against ClusterRoleBinding objects.
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &rbac.ClusterRoleBinding{} },
NewListFunc: func() runtime.Object { return &rbac.ClusterRoleBindingList{} },
DefaultQualifiedResource: rbac.Resource("clusterrolebindings"),
CreateStrategy: clusterrolebinding.Strategy,
UpdateStrategy: clusterrolebinding.Strategy,
DeleteStrategy: clusterrolebinding.Strategy,
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil {
panic(err) // TODO: Propagate error up
}
return &REST{store}
}

View File

@ -0,0 +1,95 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clusterrolebinding
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
)
// strategy implements behavior for ClusterRoleBindings
type strategy struct {
runtime.ObjectTyper
names.NameGenerator
}
// strategy is the default logic that applies when creating and updating
// ClusterRoleBinding objects.
var Strategy = strategy{legacyscheme.Scheme, names.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(ctx genericapirequest.Context, obj runtime.Object) {
_ = obj.(*rbac.ClusterRoleBinding)
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (strategy) PrepareForUpdate(ctx genericapirequest.Context, 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 genericapirequest.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 genericapirequest.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
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rbac
import (
"fmt"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/apis/rbac"
)
func EscalationAllowed(ctx genericapirequest.Context) bool {
u, ok := genericapirequest.UserFrom(ctx)
if !ok {
return false
}
// system:masters is special because the API server uses it for privileged loopback connections
// therefore we know that a member of system:masters can always do anything
for _, group := range u.GetGroups() {
if group == user.SystemPrivilegedGroup {
return true
}
}
return false
}
// BindingAuthorized returns true if the user associated with the context is explicitly authorized to bind the specified roleRef
func BindingAuthorized(ctx genericapirequest.Context, roleRef rbac.RoleRef, bindingNamespace string, a authorizer.Authorizer) bool {
if a == nil {
return false
}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return false
}
attrs := authorizer.AttributesRecord{
User: user,
Verb: "bind",
// check against the namespace where the binding is being created (or the empty namespace for clusterrolebindings).
// this allows delegation to bind particular clusterroles in rolebindings within particular namespaces,
// and to authorize binding a clusterrole across all namespaces in a clusterrolebinding.
Namespace: bindingNamespace,
ResourceRequest: true,
}
// This occurs after defaulting and conversion, so values pulled from the roleRef won't change
// Invalid APIGroup or Name values will fail validation
switch roleRef.Kind {
case "ClusterRole":
attrs.APIGroup = roleRef.APIGroup
attrs.Resource = "clusterroles"
attrs.Name = roleRef.Name
case "Role":
attrs.APIGroup = roleRef.APIGroup
attrs.Resource = "roles"
attrs.Name = roleRef.Name
default:
return false
}
decision, _, err := a.Authorize(attrs)
if err != nil {
utilruntime.HandleError(fmt.Errorf(
"error authorizing user %#v to bind %#v in namespace %s: %v",
user, roleRef, bindingNamespace, err,
))
}
return decision == authorizer.DecisionAllow
}

49
vendor/k8s.io/kubernetes/pkg/registry/rbac/helpers.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rbac
import (
"reflect"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
)
// IsOnlyMutatingGCFields checks finalizers and ownerrefs which GC manipulates
// and indicates that only those fields are changing
func IsOnlyMutatingGCFields(obj, old runtime.Object, equalities conversion.Equalities) bool {
if old == nil || reflect.ValueOf(old).IsNil() {
return false
}
// make a copy of the newObj so that we can stomp for comparison
copied := obj.DeepCopyObject()
copiedMeta, err := meta.Accessor(copied)
if err != nil {
return false
}
oldMeta, err := meta.Accessor(old)
if err != nil {
return false
}
copiedMeta.SetOwnerReferences(oldMeta.GetOwnerReferences())
copiedMeta.SetFinalizers(oldMeta.GetFinalizers())
copiedMeta.SetSelfLink(oldMeta.GetSelfLink())
return equalities.DeepEqual(copied, old)
}

View File

@ -0,0 +1,152 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rbac
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kapi "k8s.io/kubernetes/pkg/apis/core"
kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
)
func newPod() *kapi.Pod {
return &kapi.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{},
Name: "foo",
OwnerReferences: []metav1.OwnerReference{},
},
}
}
func TestIsOnlyMutatingGCFields(t *testing.T) {
tests := []struct {
name string
obj func() runtime.Object
old func() runtime.Object
expected bool
}{
{
name: "same",
obj: func() runtime.Object {
return newPod()
},
old: func() runtime.Object {
return newPod()
},
expected: true,
},
{
name: "only annotations",
obj: func() runtime.Object {
obj := newPod()
obj.Annotations["foo"] = "bar"
return obj
},
old: func() runtime.Object {
return newPod()
},
expected: false,
},
{
name: "only other",
obj: func() runtime.Object {
obj := newPod()
obj.Spec.RestartPolicy = kapi.RestartPolicyAlways
return obj
},
old: func() runtime.Object {
return newPod()
},
expected: false,
},
{
name: "only ownerRef",
obj: func() runtime.Object {
obj := newPod()
obj.OwnerReferences = append(obj.OwnerReferences, metav1.OwnerReference{Name: "foo"})
return obj
},
old: func() runtime.Object {
return newPod()
},
expected: true,
},
{
name: "ownerRef and finalizer",
obj: func() runtime.Object {
obj := newPod()
obj.OwnerReferences = append(obj.OwnerReferences, metav1.OwnerReference{Name: "foo"})
obj.Finalizers = []string{"final"}
return obj
},
old: func() runtime.Object {
return newPod()
},
expected: true,
},
{
name: "and annotations",
obj: func() runtime.Object {
obj := newPod()
obj.OwnerReferences = append(obj.OwnerReferences, metav1.OwnerReference{Name: "foo"})
obj.Annotations["foo"] = "bar"
return obj
},
old: func() runtime.Object {
return newPod()
},
expected: false,
},
{
name: "and other",
obj: func() runtime.Object {
obj := newPod()
obj.OwnerReferences = append(obj.OwnerReferences, metav1.OwnerReference{Name: "foo"})
obj.Spec.RestartPolicy = kapi.RestartPolicyAlways
return obj
},
old: func() runtime.Object {
return newPod()
},
expected: false,
},
{
name: "and nil",
obj: func() runtime.Object {
obj := newPod()
obj.OwnerReferences = append(obj.OwnerReferences, metav1.OwnerReference{Name: "foo"})
obj.Spec.RestartPolicy = kapi.RestartPolicyAlways
return obj
},
old: func() runtime.Object {
return (*kapi.Pod)(nil)
},
expected: false,
},
}
for _, tc := range tests {
actual := IsOnlyMutatingGCFields(tc.obj(), tc.old(), kapihelper.Semantic)
if tc.expected != actual {
t.Errorf("%s: expected %v, got %v", tc.name, tc.expected, actual)
}
}
}

View File

@ -0,0 +1,62 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"reconcile_role_test.go",
"reconcile_rolebindings_test.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/reconciliation",
library = ":go_default_library",
deps = [
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"clusterrole_interfaces.go",
"clusterrolebinding_interfaces.go",
"reconcile_role.go",
"reconcile_rolebindings.go",
"role_interfaces.go",
"rolebinding_interfaces.go",
"zz_generated.deepcopy.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/reconciliation",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,104 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
)
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/pkg/registry/rbac/reconciliation.RuleOwner
// +k8s:deepcopy-gen:nonpointer-interfaces=true
type ClusterRoleRuleOwner struct {
ClusterRole *rbac.ClusterRole
}
func (o ClusterRoleRuleOwner) GetObject() runtime.Object {
return o.ClusterRole
}
func (o ClusterRoleRuleOwner) GetNamespace() string {
return o.ClusterRole.Namespace
}
func (o ClusterRoleRuleOwner) GetName() string {
return o.ClusterRole.Name
}
func (o ClusterRoleRuleOwner) GetLabels() map[string]string {
return o.ClusterRole.Labels
}
func (o ClusterRoleRuleOwner) SetLabels(in map[string]string) {
o.ClusterRole.Labels = in
}
func (o ClusterRoleRuleOwner) GetAnnotations() map[string]string {
return o.ClusterRole.Annotations
}
func (o ClusterRoleRuleOwner) SetAnnotations(in map[string]string) {
o.ClusterRole.Annotations = in
}
func (o ClusterRoleRuleOwner) GetRules() []rbac.PolicyRule {
return o.ClusterRole.Rules
}
func (o ClusterRoleRuleOwner) SetRules(in []rbac.PolicyRule) {
o.ClusterRole.Rules = in
}
func (o ClusterRoleRuleOwner) GetAggregationRule() *rbac.AggregationRule {
return o.ClusterRole.AggregationRule
}
func (o ClusterRoleRuleOwner) SetAggregationRule(in *rbac.AggregationRule) {
o.ClusterRole.AggregationRule = in
}
type ClusterRoleModifier struct {
Client internalversion.ClusterRoleInterface
}
func (c ClusterRoleModifier) Get(namespace, name string) (RuleOwner, error) {
ret, err := c.Client.Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return ClusterRoleRuleOwner{ClusterRole: ret}, err
}
func (c ClusterRoleModifier) Create(in RuleOwner) (RuleOwner, error) {
ret, err := c.Client.Create(in.(ClusterRoleRuleOwner).ClusterRole)
if err != nil {
return nil, err
}
return ClusterRoleRuleOwner{ClusterRole: ret}, err
}
func (c ClusterRoleModifier) Update(in RuleOwner) (RuleOwner, error) {
ret, err := c.Client.Update(in.(ClusterRoleRuleOwner).ClusterRole)
if err != nil {
return nil, err
}
return ClusterRoleRuleOwner{ClusterRole: ret}, err
}

View File

@ -0,0 +1,109 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
)
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/pkg/registry/rbac/reconciliation.RoleBinding
// +k8s:deepcopy-gen:nonpointer-interfaces=true
type ClusterRoleBindingAdapter struct {
ClusterRoleBinding *rbac.ClusterRoleBinding
}
func (o ClusterRoleBindingAdapter) GetObject() runtime.Object {
return o.ClusterRoleBinding
}
func (o ClusterRoleBindingAdapter) GetNamespace() string {
return o.ClusterRoleBinding.Namespace
}
func (o ClusterRoleBindingAdapter) GetName() string {
return o.ClusterRoleBinding.Name
}
func (o ClusterRoleBindingAdapter) GetUID() types.UID {
return o.ClusterRoleBinding.UID
}
func (o ClusterRoleBindingAdapter) GetLabels() map[string]string {
return o.ClusterRoleBinding.Labels
}
func (o ClusterRoleBindingAdapter) SetLabels(in map[string]string) {
o.ClusterRoleBinding.Labels = in
}
func (o ClusterRoleBindingAdapter) GetAnnotations() map[string]string {
return o.ClusterRoleBinding.Annotations
}
func (o ClusterRoleBindingAdapter) SetAnnotations(in map[string]string) {
o.ClusterRoleBinding.Annotations = in
}
func (o ClusterRoleBindingAdapter) GetRoleRef() rbac.RoleRef {
return o.ClusterRoleBinding.RoleRef
}
func (o ClusterRoleBindingAdapter) GetSubjects() []rbac.Subject {
return o.ClusterRoleBinding.Subjects
}
func (o ClusterRoleBindingAdapter) SetSubjects(in []rbac.Subject) {
o.ClusterRoleBinding.Subjects = in
}
type ClusterRoleBindingClientAdapter struct {
Client internalversion.ClusterRoleBindingInterface
}
func (c ClusterRoleBindingClientAdapter) Get(namespace, name string) (RoleBinding, error) {
ret, err := c.Client.Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return ClusterRoleBindingAdapter{ClusterRoleBinding: ret}, err
}
func (c ClusterRoleBindingClientAdapter) Create(in RoleBinding) (RoleBinding, error) {
ret, err := c.Client.Create(in.(ClusterRoleBindingAdapter).ClusterRoleBinding)
if err != nil {
return nil, err
}
return ClusterRoleBindingAdapter{ClusterRoleBinding: ret}, err
}
func (c ClusterRoleBindingClientAdapter) Update(in RoleBinding) (RoleBinding, error) {
ret, err := c.Client.Update(in.(ClusterRoleBindingAdapter).ClusterRoleBinding)
if err != nil {
return nil, err
}
return ClusterRoleBindingAdapter{ClusterRoleBinding: ret}, err
}
func (c ClusterRoleBindingClientAdapter) Delete(namespace, name string, uid types.UID) error {
return c.Client.Delete(name, &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}})
}

View File

@ -0,0 +1,281 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
"fmt"
"reflect"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/registry/rbac/validation"
)
type ReconcileOperation string
var (
ReconcileCreate ReconcileOperation = "create"
ReconcileUpdate ReconcileOperation = "update"
ReconcileRecreate ReconcileOperation = "recreate"
ReconcileNone ReconcileOperation = "none"
)
type RuleOwnerModifier interface {
Get(namespace, name string) (RuleOwner, error)
Create(RuleOwner) (RuleOwner, error)
Update(RuleOwner) (RuleOwner, error)
}
type RuleOwner interface {
GetObject() runtime.Object
GetNamespace() string
GetName() string
GetLabels() map[string]string
SetLabels(map[string]string)
GetAnnotations() map[string]string
SetAnnotations(map[string]string)
GetRules() []rbac.PolicyRule
SetRules([]rbac.PolicyRule)
GetAggregationRule() *rbac.AggregationRule
SetAggregationRule(*rbac.AggregationRule)
DeepCopyRuleOwner() RuleOwner
}
type ReconcileRoleOptions struct {
// Role is the expected role that will be reconciled
Role RuleOwner
// Confirm indicates writes should be performed. When false, results are returned as a dry-run.
Confirm bool
// RemoveExtraPermissions indicates reconciliation should remove extra permissions from an existing role
RemoveExtraPermissions bool
// Client is used to look up existing roles, and create/update the role when Confirm=true
Client RuleOwnerModifier
}
type ReconcileClusterRoleResult struct {
// Role is the reconciled role from the reconciliation operation.
// If the reconcile was performed as a dry-run, or the existing role was protected, the reconciled role is not persisted.
Role RuleOwner
// MissingRules contains expected rules that were missing from the currently persisted role
MissingRules []rbac.PolicyRule
// ExtraRules contains extra permissions the currently persisted role had
ExtraRules []rbac.PolicyRule
// MissingAggregationRuleSelectors contains expected selectors that were missing from the currently persisted role
MissingAggregationRuleSelectors []metav1.LabelSelector
// ExtraAggregationRuleSelectors contains extra selectors the currently persisted role had
ExtraAggregationRuleSelectors []metav1.LabelSelector
// Operation is the API operation required to reconcile.
// If no reconciliation was needed, it is set to ReconcileNone.
// If options.Confirm == false, the reconcile was in dry-run mode, so the operation was not performed.
// If result.Protected == true, the role opted out of reconciliation, so the operation was not performed.
// Otherwise, the operation was performed.
Operation ReconcileOperation
// Protected indicates an existing role prevented reconciliation
Protected bool
}
func (o *ReconcileRoleOptions) Run() (*ReconcileClusterRoleResult, error) {
return o.run(0)
}
func (o *ReconcileRoleOptions) run(attempts int) (*ReconcileClusterRoleResult, error) {
// This keeps us from retrying forever if a role keeps appearing and disappearing as we reconcile.
// Conflict errors on update are handled at a higher level.
if attempts > 2 {
return nil, fmt.Errorf("exceeded maximum attempts")
}
var result *ReconcileClusterRoleResult
existing, err := o.Client.Get(o.Role.GetNamespace(), o.Role.GetName())
switch {
case errors.IsNotFound(err):
aggregationRule := o.Role.GetAggregationRule()
if aggregationRule == nil {
aggregationRule = &rbac.AggregationRule{}
}
result = &ReconcileClusterRoleResult{
Role: o.Role,
MissingRules: o.Role.GetRules(),
MissingAggregationRuleSelectors: aggregationRule.ClusterRoleSelectors,
Operation: ReconcileCreate,
}
case err != nil:
return nil, err
default:
result, err = computeReconciledRole(existing, o.Role, o.RemoveExtraPermissions)
if err != nil {
return nil, err
}
}
// If reconcile-protected, short-circuit
if result.Protected {
return result, nil
}
// If we're in dry-run mode, short-circuit
if !o.Confirm {
return result, nil
}
switch result.Operation {
case ReconcileCreate:
created, err := o.Client.Create(result.Role)
// If created since we started this reconcile, re-run
if errors.IsAlreadyExists(err) {
return o.run(attempts + 1)
}
if err != nil {
return nil, err
}
result.Role = created
case ReconcileUpdate:
updated, err := o.Client.Update(result.Role)
// If deleted since we started this reconcile, re-run
if errors.IsNotFound(err) {
return o.run(attempts + 1)
}
if err != nil {
return nil, err
}
result.Role = updated
case ReconcileNone:
// no-op
default:
return nil, fmt.Errorf("invalid operation: %v", result.Operation)
}
return result, nil
}
// computeReconciledRole returns the role that must be created and/or updated to make the
// existing role's permissions match the expected role's permissions
func computeReconciledRole(existing, expected RuleOwner, removeExtraPermissions bool) (*ReconcileClusterRoleResult, error) {
result := &ReconcileClusterRoleResult{Operation: ReconcileNone}
result.Protected = (existing.GetAnnotations()[rbac.AutoUpdateAnnotationKey] == "false")
// Start with a copy of the existing object
result.Role = existing.DeepCopyRuleOwner()
// Merge expected annotations and labels
result.Role.SetAnnotations(merge(expected.GetAnnotations(), result.Role.GetAnnotations()))
if !reflect.DeepEqual(result.Role.GetAnnotations(), existing.GetAnnotations()) {
result.Operation = ReconcileUpdate
}
result.Role.SetLabels(merge(expected.GetLabels(), result.Role.GetLabels()))
if !reflect.DeepEqual(result.Role.GetLabels(), existing.GetLabels()) {
result.Operation = ReconcileUpdate
}
// Compute extra and missing rules
_, result.ExtraRules = validation.Covers(expected.GetRules(), existing.GetRules())
_, result.MissingRules = validation.Covers(existing.GetRules(), expected.GetRules())
switch {
case !removeExtraPermissions && len(result.MissingRules) > 0:
// add missing rules in the union case
result.Role.SetRules(append(result.Role.GetRules(), result.MissingRules...))
result.Operation = ReconcileUpdate
case removeExtraPermissions && (len(result.MissingRules) > 0 || len(result.ExtraRules) > 0):
// stomp to expected rules in the non-union case
result.Role.SetRules(expected.GetRules())
result.Operation = ReconcileUpdate
}
// Compute extra and missing rules
_, result.ExtraAggregationRuleSelectors = aggregationRuleCovers(expected.GetAggregationRule(), existing.GetAggregationRule())
_, result.MissingAggregationRuleSelectors = aggregationRuleCovers(existing.GetAggregationRule(), expected.GetAggregationRule())
switch {
case !removeExtraPermissions && len(result.MissingAggregationRuleSelectors) > 0:
// add missing rules in the union case
aggregationRule := result.Role.GetAggregationRule()
if aggregationRule == nil {
aggregationRule = &rbac.AggregationRule{}
}
aggregationRule.ClusterRoleSelectors = append(aggregationRule.ClusterRoleSelectors, result.MissingAggregationRuleSelectors...)
result.Role.SetAggregationRule(aggregationRule)
result.Operation = ReconcileUpdate
case removeExtraPermissions && (len(result.MissingAggregationRuleSelectors) > 0 || len(result.ExtraAggregationRuleSelectors) > 0):
result.Role.SetAggregationRule(expected.GetAggregationRule())
result.Operation = ReconcileUpdate
}
return result, nil
}
// merge combines the given maps with the later annotations having higher precedence
func merge(maps ...map[string]string) map[string]string {
var output map[string]string = nil
for _, m := range maps {
if m != nil && output == nil {
output = map[string]string{}
}
for k, v := range m {
output[k] = v
}
}
return output
}
// aggregationRuleCovers determines whether or not the ownerSelectors cover the servantSelectors in terms of semantically
// equal label selectors.
// It returns whether or not the ownerSelectors cover and a list of the rules that the ownerSelectors do not cover.
func aggregationRuleCovers(ownerRule, servantRule *rbac.AggregationRule) (bool, []metav1.LabelSelector) {
switch {
case ownerRule == nil && servantRule == nil:
return true, []metav1.LabelSelector{}
case ownerRule == nil && servantRule != nil:
return false, servantRule.ClusterRoleSelectors
case ownerRule != nil && servantRule == nil:
return true, []metav1.LabelSelector{}
}
ownerSelectors := ownerRule.ClusterRoleSelectors
servantSelectors := servantRule.ClusterRoleSelectors
uncoveredSelectors := []metav1.LabelSelector{}
for _, servantSelector := range servantSelectors {
covered := false
for _, ownerSelector := range ownerSelectors {
if equality.Semantic.DeepEqual(ownerSelector, servantSelector) {
covered = true
break
}
}
if !covered {
uncoveredSelectors = append(uncoveredSelectors, servantSelector)
}
}
return (len(uncoveredSelectors) == 0), uncoveredSelectors
}

View File

@ -0,0 +1,372 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
)
func role(rules []rbac.PolicyRule, labels map[string]string, annotations map[string]string) *rbac.ClusterRole {
return &rbac.ClusterRole{
Rules: rules,
ObjectMeta: metav1.ObjectMeta{Labels: labels, Annotations: annotations},
}
}
func rules(resources ...string) []rbac.PolicyRule {
r := []rbac.PolicyRule{}
for _, resource := range resources {
r = append(r, rbac.PolicyRule{APIGroups: []string{""}, Verbs: []string{"get"}, Resources: []string{resource}})
}
return r
}
type ss map[string]string
func TestComputeReconciledRoleRules(t *testing.T) {
tests := map[string]struct {
expectedRole *rbac.ClusterRole
actualRole *rbac.ClusterRole
removeExtraPermissions bool
expectedReconciledRole *rbac.ClusterRole
expectedReconciliationNeeded bool
}{
"empty": {
expectedRole: role(rules(), nil, nil),
actualRole: role(rules(), nil, nil),
removeExtraPermissions: true,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"match without union": {
expectedRole: role(rules("a"), nil, nil),
actualRole: role(rules("a"), nil, nil),
removeExtraPermissions: true,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"match with union": {
expectedRole: role(rules("a"), nil, nil),
actualRole: role(rules("a"), nil, nil),
removeExtraPermissions: false,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"different rules without union": {
expectedRole: role(rules("a"), nil, nil),
actualRole: role(rules("b"), nil, nil),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), nil, nil),
expectedReconciliationNeeded: true,
},
"different rules with union": {
expectedRole: role(rules("a"), nil, nil),
actualRole: role(rules("b"), nil, nil),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("b", "a"), nil, nil),
expectedReconciliationNeeded: true,
},
"match labels without union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("a"), ss{"1": "a"}, nil),
removeExtraPermissions: true,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"match labels with union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("a"), ss{"1": "a"}, nil),
removeExtraPermissions: false,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"different labels without union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("a"), ss{"2": "b"}, nil),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), ss{"1": "a", "2": "b"}, nil),
expectedReconciliationNeeded: true,
},
"different labels with union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("a"), ss{"2": "b"}, nil),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("a"), ss{"1": "a", "2": "b"}, nil),
expectedReconciliationNeeded: true,
},
"different labels and rules without union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("b"), ss{"2": "b"}, nil),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), ss{"1": "a", "2": "b"}, nil),
expectedReconciliationNeeded: true,
},
"different labels and rules with union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("b"), ss{"2": "b"}, nil),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("b", "a"), ss{"1": "a", "2": "b"}, nil),
expectedReconciliationNeeded: true,
},
"conflicting labels and rules without union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("b"), ss{"1": "b"}, nil),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), ss{"1": "b"}, nil),
expectedReconciliationNeeded: true,
},
"conflicting labels and rules with union": {
expectedRole: role(rules("a"), ss{"1": "a"}, nil),
actualRole: role(rules("b"), ss{"1": "b"}, nil),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("b", "a"), ss{"1": "b"}, nil),
expectedReconciliationNeeded: true,
},
"match annotations without union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("a"), nil, ss{"1": "a"}),
removeExtraPermissions: true,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"match annotations with union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("a"), nil, ss{"1": "a"}),
removeExtraPermissions: false,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"different annotations without union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("a"), nil, ss{"2": "b"}),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), nil, ss{"1": "a", "2": "b"}),
expectedReconciliationNeeded: true,
},
"different annotations with union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("a"), nil, ss{"2": "b"}),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("a"), nil, ss{"1": "a", "2": "b"}),
expectedReconciliationNeeded: true,
},
"different annotations and rules without union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("b"), nil, ss{"2": "b"}),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), nil, ss{"1": "a", "2": "b"}),
expectedReconciliationNeeded: true,
},
"different annotations and rules with union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("b"), nil, ss{"2": "b"}),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("b", "a"), nil, ss{"1": "a", "2": "b"}),
expectedReconciliationNeeded: true,
},
"conflicting annotations and rules without union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("b"), nil, ss{"1": "b"}),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), nil, ss{"1": "b"}),
expectedReconciliationNeeded: true,
},
"conflicting annotations and rules with union": {
expectedRole: role(rules("a"), nil, ss{"1": "a"}),
actualRole: role(rules("b"), nil, ss{"1": "b"}),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("b", "a"), nil, ss{"1": "b"}),
expectedReconciliationNeeded: true,
},
"conflicting labels/annotations and rules without union": {
expectedRole: role(rules("a"), ss{"3": "d"}, ss{"1": "a"}),
actualRole: role(rules("b"), ss{"4": "e"}, ss{"1": "b"}),
removeExtraPermissions: true,
expectedReconciledRole: role(rules("a"), ss{"3": "d", "4": "e"}, ss{"1": "b"}),
expectedReconciliationNeeded: true,
},
"conflicting labels/annotations and rules with union": {
expectedRole: role(rules("a"), ss{"3": "d"}, ss{"1": "a"}),
actualRole: role(rules("b"), ss{"4": "e"}, ss{"1": "b"}),
removeExtraPermissions: false,
expectedReconciledRole: role(rules("b", "a"), ss{"3": "d", "4": "e"}, ss{"1": "b"}),
expectedReconciliationNeeded: true,
},
"complex labels/annotations and rules without union": {
expectedRole: role(rules("pods", "nodes", "secrets"), ss{"env": "prod", "color": "blue"}, ss{"description": "fancy", "system": "true"}),
actualRole: role(rules("nodes", "images", "projects"), ss{"color": "red", "team": "pm"}, ss{"system": "false", "owner": "admin", "vip": "yes"}),
removeExtraPermissions: true,
expectedReconciledRole: role(
rules("pods", "nodes", "secrets"),
ss{"env": "prod", "color": "red", "team": "pm"},
ss{"description": "fancy", "system": "false", "owner": "admin", "vip": "yes"}),
expectedReconciliationNeeded: true,
},
"complex labels/annotations and rules with union": {
expectedRole: role(rules("pods", "nodes", "secrets"), ss{"env": "prod", "color": "blue", "manager": "randy"}, ss{"description": "fancy", "system": "true", "up": "true"}),
actualRole: role(rules("nodes", "images", "projects"), ss{"color": "red", "team": "pm"}, ss{"system": "false", "owner": "admin", "vip": "yes", "rate": "down"}),
removeExtraPermissions: false,
expectedReconciledRole: role(
rules("nodes", "images", "projects", "pods", "secrets"),
ss{"env": "prod", "manager": "randy", "color": "red", "team": "pm"},
ss{"description": "fancy", "system": "false", "owner": "admin", "vip": "yes", "rate": "down", "up": "true"}),
expectedReconciliationNeeded: true,
},
}
for k, tc := range tests {
actualRole := ClusterRoleRuleOwner{ClusterRole: tc.actualRole}
expectedRole := ClusterRoleRuleOwner{ClusterRole: tc.expectedRole}
result, err := computeReconciledRole(actualRole, expectedRole, tc.removeExtraPermissions)
if err != nil {
t.Errorf("%s: %v", k, err)
continue
}
reconciliationNeeded := result.Operation != ReconcileNone
if reconciliationNeeded != tc.expectedReconciliationNeeded {
t.Errorf("%s: Expected\n\t%v\ngot\n\t%v", k, tc.expectedReconciliationNeeded, reconciliationNeeded)
continue
}
if reconciliationNeeded && !helper.Semantic.DeepEqual(result.Role.(ClusterRoleRuleOwner).ClusterRole, tc.expectedReconciledRole) {
t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, tc.expectedReconciledRole, result.Role)
}
}
}
func aggregatedRole(aggregationRule *rbac.AggregationRule) *rbac.ClusterRole {
return &rbac.ClusterRole{
AggregationRule: aggregationRule,
}
}
func aggregationrule(selectors []map[string]string) *rbac.AggregationRule {
ret := &rbac.AggregationRule{}
for _, selector := range selectors {
ret.ClusterRoleSelectors = append(ret.ClusterRoleSelectors,
metav1.LabelSelector{MatchLabels: selector})
}
return ret
}
func TestComputeReconciledRoleAggregationRules(t *testing.T) {
tests := map[string]struct {
expectedRole *rbac.ClusterRole
actualRole *rbac.ClusterRole
removeExtraPermissions bool
expectedReconciledRole *rbac.ClusterRole
expectedReconciliationNeeded bool
}{
"empty": {
expectedRole: aggregatedRole(&rbac.AggregationRule{}),
actualRole: aggregatedRole(nil),
removeExtraPermissions: true,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"empty-2": {
expectedRole: aggregatedRole(&rbac.AggregationRule{}),
actualRole: aggregatedRole(&rbac.AggregationRule{}),
removeExtraPermissions: true,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"match without union": {
expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})),
actualRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})),
removeExtraPermissions: true,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"match with union": {
expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})),
actualRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})),
removeExtraPermissions: false,
expectedReconciledRole: nil,
expectedReconciliationNeeded: false,
},
"different rules without union": {
expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})),
actualRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}})),
removeExtraPermissions: true,
expectedReconciledRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})),
expectedReconciliationNeeded: true,
},
"different rules with union": {
expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})),
actualRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}})),
removeExtraPermissions: false,
expectedReconciledRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}, {"foo": "bar"}})),
expectedReconciliationNeeded: true,
},
}
for k, tc := range tests {
actualRole := ClusterRoleRuleOwner{ClusterRole: tc.actualRole}
expectedRole := ClusterRoleRuleOwner{ClusterRole: tc.expectedRole}
result, err := computeReconciledRole(actualRole, expectedRole, tc.removeExtraPermissions)
if err != nil {
t.Errorf("%s: %v", k, err)
continue
}
reconciliationNeeded := result.Operation != ReconcileNone
if reconciliationNeeded != tc.expectedReconciliationNeeded {
t.Errorf("%s: Expected\n\t%v\ngot\n\t%v", k, tc.expectedReconciliationNeeded, reconciliationNeeded)
continue
}
if reconciliationNeeded && !helper.Semantic.DeepEqual(result.Role.(ClusterRoleRuleOwner).ClusterRole, tc.expectedReconciledRole) {
t.Errorf("%s: %v", k, diff.ObjectDiff(tc.expectedReconciledRole, result.Role.(ClusterRoleRuleOwner).ClusterRole))
}
}
}

View File

@ -0,0 +1,248 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
"fmt"
"reflect"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/apis/rbac"
)
type RoleBindingModifier interface {
Get(namespace, name string) (RoleBinding, error)
Delete(namespace, name string, uid types.UID) error
Create(RoleBinding) (RoleBinding, error)
Update(RoleBinding) (RoleBinding, error)
}
type RoleBinding interface {
GetObject() runtime.Object
GetNamespace() string
GetName() string
GetUID() types.UID
GetLabels() map[string]string
SetLabels(map[string]string)
GetAnnotations() map[string]string
SetAnnotations(map[string]string)
GetRoleRef() rbac.RoleRef
GetSubjects() []rbac.Subject
SetSubjects([]rbac.Subject)
DeepCopyRoleBinding() RoleBinding
}
// ReconcileRoleBindingOptions holds options for running a role binding reconciliation
type ReconcileRoleBindingOptions struct {
// RoleBinding is the expected rolebinding that will be reconciled
RoleBinding RoleBinding
// Confirm indicates writes should be performed. When false, results are returned as a dry-run.
Confirm bool
// RemoveExtraSubjects indicates reconciliation should remove extra subjects from an existing role binding
RemoveExtraSubjects bool
// Client is used to look up existing rolebindings, and create/update the rolebinding when Confirm=true
Client RoleBindingModifier
}
// ReconcileClusterRoleBindingResult holds the result of a reconciliation operation.
type ReconcileClusterRoleBindingResult struct {
// RoleBinding is the reconciled rolebinding from the reconciliation operation.
// If the reconcile was performed as a dry-run, or the existing rolebinding was protected, the reconciled rolebinding is not persisted.
RoleBinding RoleBinding
// MissingSubjects contains expected subjects that were missing from the currently persisted rolebinding
MissingSubjects []rbac.Subject
// ExtraSubjects contains extra subjects the currently persisted rolebinding had
ExtraSubjects []rbac.Subject
// Operation is the API operation required to reconcile.
// If no reconciliation was needed, it is set to ReconcileNone.
// If options.Confirm == false, the reconcile was in dry-run mode, so the operation was not performed.
// If result.Protected == true, the rolebinding opted out of reconciliation, so the operation was not performed.
// Otherwise, the operation was performed.
Operation ReconcileOperation
// Protected indicates an existing role prevented reconciliation
Protected bool
}
func (o *ReconcileRoleBindingOptions) Run() (*ReconcileClusterRoleBindingResult, error) {
return o.run(0)
}
func (o *ReconcileRoleBindingOptions) run(attempts int) (*ReconcileClusterRoleBindingResult, error) {
// This keeps us from retrying forever if a rolebinding keeps appearing and disappearing as we reconcile.
// Conflict errors on update are handled at a higher level.
if attempts > 3 {
return nil, fmt.Errorf("exceeded maximum attempts")
}
var result *ReconcileClusterRoleBindingResult
existingBinding, err := o.Client.Get(o.RoleBinding.GetNamespace(), o.RoleBinding.GetName())
switch {
case errors.IsNotFound(err):
result = &ReconcileClusterRoleBindingResult{
RoleBinding: o.RoleBinding,
MissingSubjects: o.RoleBinding.GetSubjects(),
Operation: ReconcileCreate,
}
case err != nil:
return nil, err
default:
result, err = computeReconciledRoleBinding(existingBinding, o.RoleBinding, o.RemoveExtraSubjects)
if err != nil {
return nil, err
}
}
// If reconcile-protected, short-circuit
if result.Protected {
return result, nil
}
// If we're in dry-run mode, short-circuit
if !o.Confirm {
return result, nil
}
switch result.Operation {
case ReconcileRecreate:
// Try deleting
err := o.Client.Delete(existingBinding.GetNamespace(), existingBinding.GetName(), existingBinding.GetUID())
switch {
case err == nil, errors.IsNotFound(err):
// object no longer exists, as desired
case errors.IsConflict(err):
// delete failed because our UID precondition conflicted
// this could mean another object exists with a different UID, re-run
return o.run(attempts + 1)
default:
// return other errors
return nil, err
}
// continue to create
fallthrough
case ReconcileCreate:
created, err := o.Client.Create(result.RoleBinding)
// If created since we started this reconcile, re-run
if errors.IsAlreadyExists(err) {
return o.run(attempts + 1)
}
if err != nil {
return nil, err
}
result.RoleBinding = created
case ReconcileUpdate:
updated, err := o.Client.Update(result.RoleBinding)
// If deleted since we started this reconcile, re-run
if errors.IsNotFound(err) {
return o.run(attempts + 1)
}
if err != nil {
return nil, err
}
result.RoleBinding = updated
case ReconcileNone:
// no-op
default:
return nil, fmt.Errorf("invalid operation: %v", result.Operation)
}
return result, nil
}
// computeReconciledRoleBinding returns the rolebinding that must be created and/or updated to make the
// existing rolebinding's subjects, roleref, labels, and annotations match the expected rolebinding
func computeReconciledRoleBinding(existing, expected RoleBinding, removeExtraSubjects bool) (*ReconcileClusterRoleBindingResult, error) {
result := &ReconcileClusterRoleBindingResult{Operation: ReconcileNone}
result.Protected = (existing.GetAnnotations()[rbac.AutoUpdateAnnotationKey] == "false")
// Reset the binding completely if the roleRef is different
if expected.GetRoleRef() != existing.GetRoleRef() {
result.RoleBinding = expected
result.Operation = ReconcileRecreate
return result, nil
}
// Start with a copy of the existing object
result.RoleBinding = existing.DeepCopyRoleBinding()
// Merge expected annotations and labels
result.RoleBinding.SetAnnotations(merge(expected.GetAnnotations(), result.RoleBinding.GetAnnotations()))
if !reflect.DeepEqual(result.RoleBinding.GetAnnotations(), existing.GetAnnotations()) {
result.Operation = ReconcileUpdate
}
result.RoleBinding.SetLabels(merge(expected.GetLabels(), result.RoleBinding.GetLabels()))
if !reflect.DeepEqual(result.RoleBinding.GetLabels(), existing.GetLabels()) {
result.Operation = ReconcileUpdate
}
// Compute extra and missing subjects
result.MissingSubjects, result.ExtraSubjects = diffSubjectLists(expected.GetSubjects(), existing.GetSubjects())
switch {
case !removeExtraSubjects && len(result.MissingSubjects) > 0:
// add missing subjects in the union case
result.RoleBinding.SetSubjects(append(result.RoleBinding.GetSubjects(), result.MissingSubjects...))
result.Operation = ReconcileUpdate
case removeExtraSubjects && (len(result.MissingSubjects) > 0 || len(result.ExtraSubjects) > 0):
// stomp to expected subjects in the non-union case
result.RoleBinding.SetSubjects(expected.GetSubjects())
result.Operation = ReconcileUpdate
}
return result, nil
}
func contains(list []rbac.Subject, item rbac.Subject) bool {
for _, listItem := range list {
if listItem == item {
return true
}
}
return false
}
// diffSubjectLists returns lists containing the items unique to each provided list:
// list1Only = list1 - list2
// list2Only = list2 - list1
// if both returned lists are empty, the provided lists are equal
func diffSubjectLists(list1 []rbac.Subject, list2 []rbac.Subject) (list1Only []rbac.Subject, list2Only []rbac.Subject) {
for _, list1Item := range list1 {
if !contains(list2, list1Item) {
if !contains(list1Only, list1Item) {
list1Only = append(list1Only, list1Item)
}
}
}
for _, list2Item := range list2 {
if !contains(list1, list2Item) {
if !contains(list2Only, list2Item) {
list2Only = append(list2Only, list2Item)
}
}
}
return
}

View File

@ -0,0 +1,181 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
"testing"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
)
func binding(roleRef rbac.RoleRef, subjects []rbac.Subject) *rbac.ClusterRoleBinding {
return &rbac.ClusterRoleBinding{RoleRef: roleRef, Subjects: subjects}
}
func ref(name string) rbac.RoleRef {
return rbac.RoleRef{Name: name}
}
func subject(name string) rbac.Subject {
return rbac.Subject{Name: name}
}
func subjects(names ...string) []rbac.Subject {
r := []rbac.Subject{}
for _, name := range names {
r = append(r, subject(name))
}
return r
}
func TestDiffObjectReferenceLists(t *testing.T) {
tests := map[string]struct {
A []rbac.Subject
B []rbac.Subject
ExpectedOnlyA []rbac.Subject
ExpectedOnlyB []rbac.Subject
}{
"empty": {},
"matching, order-independent": {
A: subjects("foo", "bar"),
B: subjects("bar", "foo"),
},
"partial match": {
A: subjects("foo", "bar"),
B: subjects("foo", "baz"),
ExpectedOnlyA: subjects("bar"),
ExpectedOnlyB: subjects("baz"),
},
"missing": {
A: subjects("foo"),
B: subjects("bar"),
ExpectedOnlyA: subjects("foo"),
ExpectedOnlyB: subjects("bar"),
},
"remove duplicates": {
A: subjects("foo", "foo"),
B: subjects("bar", "bar"),
ExpectedOnlyA: subjects("foo"),
ExpectedOnlyB: subjects("bar"),
},
}
for k, tc := range tests {
onlyA, onlyB := diffSubjectLists(tc.A, tc.B)
if !helper.Semantic.DeepEqual(onlyA, tc.ExpectedOnlyA) {
t.Errorf("%s: Expected %#v, got %#v", k, tc.ExpectedOnlyA, onlyA)
}
if !helper.Semantic.DeepEqual(onlyB, tc.ExpectedOnlyB) {
t.Errorf("%s: Expected %#v, got %#v", k, tc.ExpectedOnlyB, onlyB)
}
}
}
func TestComputeUpdate(t *testing.T) {
tests := map[string]struct {
ExpectedBinding *rbac.ClusterRoleBinding
ActualBinding *rbac.ClusterRoleBinding
RemoveExtraSubjects bool
ExpectedUpdatedBinding *rbac.ClusterRoleBinding
ExpectedUpdateNeeded bool
}{
"match without union": {
ExpectedBinding: binding(ref("role"), subjects("a")),
ActualBinding: binding(ref("role"), subjects("a")),
RemoveExtraSubjects: true,
ExpectedUpdatedBinding: nil,
ExpectedUpdateNeeded: false,
},
"match with union": {
ExpectedBinding: binding(ref("role"), subjects("a")),
ActualBinding: binding(ref("role"), subjects("a")),
RemoveExtraSubjects: false,
ExpectedUpdatedBinding: nil,
ExpectedUpdateNeeded: false,
},
"different roleref with identical subjects": {
ExpectedBinding: binding(ref("role"), subjects("a")),
ActualBinding: binding(ref("differentRole"), subjects("a")),
RemoveExtraSubjects: false,
ExpectedUpdatedBinding: binding(ref("role"), subjects("a")),
ExpectedUpdateNeeded: true,
},
"extra subjects without union": {
ExpectedBinding: binding(ref("role"), subjects("a")),
ActualBinding: binding(ref("role"), subjects("a", "b")),
RemoveExtraSubjects: true,
ExpectedUpdatedBinding: binding(ref("role"), subjects("a")),
ExpectedUpdateNeeded: true,
},
"extra subjects with union": {
ExpectedBinding: binding(ref("role"), subjects("a")),
ActualBinding: binding(ref("role"), subjects("a", "b")),
RemoveExtraSubjects: false,
ExpectedUpdatedBinding: nil,
ExpectedUpdateNeeded: false,
},
"missing subjects without union": {
ExpectedBinding: binding(ref("role"), subjects("a", "c")),
ActualBinding: binding(ref("role"), subjects("a", "b")),
RemoveExtraSubjects: true,
ExpectedUpdatedBinding: binding(ref("role"), subjects("a", "c")),
ExpectedUpdateNeeded: true,
},
"missing subjects with union": {
ExpectedBinding: binding(ref("role"), subjects("a", "c")),
ActualBinding: binding(ref("role"), subjects("a", "b")),
RemoveExtraSubjects: false,
ExpectedUpdatedBinding: binding(ref("role"), subjects("a", "b", "c")),
ExpectedUpdateNeeded: true,
},
}
for k, tc := range tests {
actualRoleBinding := ClusterRoleBindingAdapter{ClusterRoleBinding: tc.ActualBinding}
expectedRoleBinding := ClusterRoleBindingAdapter{ClusterRoleBinding: tc.ExpectedBinding}
result, err := computeReconciledRoleBinding(actualRoleBinding, expectedRoleBinding, tc.RemoveExtraSubjects)
if err != nil {
t.Errorf("%s: %v", k, err)
continue
}
updateNeeded := result.Operation != ReconcileNone
updatedBinding := result.RoleBinding.(ClusterRoleBindingAdapter).ClusterRoleBinding
if updateNeeded != tc.ExpectedUpdateNeeded {
t.Errorf("%s: Expected\n\t%v\ngot\n\t%v (%v)", k, tc.ExpectedUpdateNeeded, updateNeeded, result.Operation)
continue
}
if updateNeeded && !helper.Semantic.DeepEqual(updatedBinding, tc.ExpectedUpdatedBinding) {
t.Errorf("%s: Expected\n\t%v %v\ngot\n\t%v %v", k, tc.ExpectedUpdatedBinding.RoleRef, tc.ExpectedUpdatedBinding.Subjects, updatedBinding.RoleRef, updatedBinding.Subjects)
}
}
}

View File

@ -0,0 +1,112 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/rbac"
core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
)
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/pkg/registry/rbac/reconciliation.RuleOwner
// +k8s:deepcopy-gen:nonpointer-interfaces=true
type RoleRuleOwner struct {
Role *rbac.Role
}
func (o RoleRuleOwner) GetObject() runtime.Object {
return o.Role
}
func (o RoleRuleOwner) GetNamespace() string {
return o.Role.Namespace
}
func (o RoleRuleOwner) GetName() string {
return o.Role.Name
}
func (o RoleRuleOwner) GetLabels() map[string]string {
return o.Role.Labels
}
func (o RoleRuleOwner) SetLabels(in map[string]string) {
o.Role.Labels = in
}
func (o RoleRuleOwner) GetAnnotations() map[string]string {
return o.Role.Annotations
}
func (o RoleRuleOwner) SetAnnotations(in map[string]string) {
o.Role.Annotations = in
}
func (o RoleRuleOwner) GetRules() []rbac.PolicyRule {
return o.Role.Rules
}
func (o RoleRuleOwner) SetRules(in []rbac.PolicyRule) {
o.Role.Rules = in
}
func (o RoleRuleOwner) GetAggregationRule() *rbac.AggregationRule {
return nil
}
func (o RoleRuleOwner) SetAggregationRule(in *rbac.AggregationRule) {
}
type RoleModifier struct {
Client internalversion.RolesGetter
NamespaceClient core.NamespaceInterface
}
func (c RoleModifier) Get(namespace, name string) (RuleOwner, error) {
ret, err := c.Client.Roles(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return RoleRuleOwner{Role: ret}, err
}
func (c RoleModifier) Create(in RuleOwner) (RuleOwner, error) {
ns := &api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: in.GetNamespace()}}
if _, err := c.NamespaceClient.Create(ns); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, err
}
ret, err := c.Client.Roles(in.GetNamespace()).Create(in.(RoleRuleOwner).Role)
if err != nil {
return nil, err
}
return RoleRuleOwner{Role: ret}, err
}
func (c RoleModifier) Update(in RuleOwner) (RuleOwner, error) {
ret, err := c.Client.Roles(in.GetNamespace()).Update(in.(RoleRuleOwner).Role)
if err != nil {
return nil, err
}
return RoleRuleOwner{Role: ret}, err
}

View File

@ -0,0 +1,118 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reconciliation
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/rbac"
core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
)
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/pkg/registry/rbac/reconciliation.RoleBinding
// +k8s:deepcopy-gen:nonpointer-interfaces=true
type RoleBindingAdapter struct {
RoleBinding *rbac.RoleBinding
}
func (o RoleBindingAdapter) GetObject() runtime.Object {
return o.RoleBinding
}
func (o RoleBindingAdapter) GetNamespace() string {
return o.RoleBinding.Namespace
}
func (o RoleBindingAdapter) GetName() string {
return o.RoleBinding.Name
}
func (o RoleBindingAdapter) GetUID() types.UID {
return o.RoleBinding.UID
}
func (o RoleBindingAdapter) GetLabels() map[string]string {
return o.RoleBinding.Labels
}
func (o RoleBindingAdapter) SetLabels(in map[string]string) {
o.RoleBinding.Labels = in
}
func (o RoleBindingAdapter) GetAnnotations() map[string]string {
return o.RoleBinding.Annotations
}
func (o RoleBindingAdapter) SetAnnotations(in map[string]string) {
o.RoleBinding.Annotations = in
}
func (o RoleBindingAdapter) GetRoleRef() rbac.RoleRef {
return o.RoleBinding.RoleRef
}
func (o RoleBindingAdapter) GetSubjects() []rbac.Subject {
return o.RoleBinding.Subjects
}
func (o RoleBindingAdapter) SetSubjects(in []rbac.Subject) {
o.RoleBinding.Subjects = in
}
type RoleBindingClientAdapter struct {
Client internalversion.RoleBindingsGetter
NamespaceClient core.NamespaceInterface
}
func (c RoleBindingClientAdapter) Get(namespace, name string) (RoleBinding, error) {
ret, err := c.Client.RoleBindings(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return RoleBindingAdapter{RoleBinding: ret}, err
}
func (c RoleBindingClientAdapter) Create(in RoleBinding) (RoleBinding, error) {
ns := &api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: in.GetNamespace()}}
if _, err := c.NamespaceClient.Create(ns); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, err
}
ret, err := c.Client.RoleBindings(in.GetNamespace()).Create(in.(RoleBindingAdapter).RoleBinding)
if err != nil {
return nil, err
}
return RoleBindingAdapter{RoleBinding: ret}, err
}
func (c RoleBindingClientAdapter) Update(in RoleBinding) (RoleBinding, error) {
ret, err := c.Client.RoleBindings(in.GetNamespace()).Update(in.(RoleBindingAdapter).RoleBinding)
if err != nil {
return nil, err
}
return RoleBindingAdapter{RoleBinding: ret}, err
}
func (c RoleBindingClientAdapter) Delete(namespace, name string, uid types.UID) error {
return c.Client.RoleBindings(namespace).Delete(name, &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}})
}

View File

@ -0,0 +1,145 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
package reconciliation
import (
rbac "k8s.io/kubernetes/pkg/apis/rbac"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterRoleBindingAdapter) DeepCopyInto(out *ClusterRoleBindingAdapter) {
*out = *in
if in.ClusterRoleBinding != nil {
in, out := &in.ClusterRoleBinding, &out.ClusterRoleBinding
if *in == nil {
*out = nil
} else {
*out = new(rbac.ClusterRoleBinding)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleBindingAdapter.
func (in *ClusterRoleBindingAdapter) DeepCopy() *ClusterRoleBindingAdapter {
if in == nil {
return nil
}
out := new(ClusterRoleBindingAdapter)
in.DeepCopyInto(out)
return out
}
// DeepCopyRoleBinding is an autogenerated deepcopy function, copying the receiver, creating a new RoleBinding.
func (in ClusterRoleBindingAdapter) DeepCopyRoleBinding() RoleBinding {
return *in.DeepCopy()
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterRoleRuleOwner) DeepCopyInto(out *ClusterRoleRuleOwner) {
*out = *in
if in.ClusterRole != nil {
in, out := &in.ClusterRole, &out.ClusterRole
if *in == nil {
*out = nil
} else {
*out = new(rbac.ClusterRole)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleRuleOwner.
func (in *ClusterRoleRuleOwner) DeepCopy() *ClusterRoleRuleOwner {
if in == nil {
return nil
}
out := new(ClusterRoleRuleOwner)
in.DeepCopyInto(out)
return out
}
// DeepCopyRuleOwner is an autogenerated deepcopy function, copying the receiver, creating a new RuleOwner.
func (in ClusterRoleRuleOwner) DeepCopyRuleOwner() RuleOwner {
return *in.DeepCopy()
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RoleBindingAdapter) DeepCopyInto(out *RoleBindingAdapter) {
*out = *in
if in.RoleBinding != nil {
in, out := &in.RoleBinding, &out.RoleBinding
if *in == nil {
*out = nil
} else {
*out = new(rbac.RoleBinding)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingAdapter.
func (in *RoleBindingAdapter) DeepCopy() *RoleBindingAdapter {
if in == nil {
return nil
}
out := new(RoleBindingAdapter)
in.DeepCopyInto(out)
return out
}
// DeepCopyRoleBinding is an autogenerated deepcopy function, copying the receiver, creating a new RoleBinding.
func (in RoleBindingAdapter) DeepCopyRoleBinding() RoleBinding {
return *in.DeepCopy()
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RoleRuleOwner) DeepCopyInto(out *RoleRuleOwner) {
*out = *in
if in.Role != nil {
in, out := &in.Role, &out.Role
if *in == nil {
*out = nil
} else {
*out = new(rbac.Role)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleRuleOwner.
func (in *RoleRuleOwner) DeepCopy() *RoleRuleOwner {
if in == nil {
return nil
}
out := new(RoleRuleOwner)
in.DeepCopyInto(out)
return out
}
// DeepCopyRuleOwner is an autogenerated deepcopy function, copying the receiver, creating a new RuleOwner.
func (in RoleRuleOwner) DeepCopyRuleOwner() RuleOwner {
return *in.DeepCopy()
}

61
vendor/k8s.io/kubernetes/pkg/registry/rbac/rest/BUILD generated vendored Normal file
View File

@ -0,0 +1,61 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage_rbac.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/rest",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
"//pkg/registry/rbac/clusterrole:go_default_library",
"//pkg/registry/rbac/clusterrole/policybased:go_default_library",
"//pkg/registry/rbac/clusterrole/storage:go_default_library",
"//pkg/registry/rbac/clusterrolebinding:go_default_library",
"//pkg/registry/rbac/clusterrolebinding/policybased:go_default_library",
"//pkg/registry/rbac/clusterrolebinding/storage:go_default_library",
"//pkg/registry/rbac/reconciliation:go_default_library",
"//pkg/registry/rbac/role:go_default_library",
"//pkg/registry/rbac/role/policybased:go_default_library",
"//pkg/registry/rbac/role/storage:go_default_library",
"//pkg/registry/rbac/rolebinding:go_default_library",
"//pkg/registry/rbac/rolebinding/policybased:go_default_library",
"//pkg/registry/rbac/rolebinding/storage:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1alpha1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
"//vendor/k8s.io/client-go/util/retry:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,353 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rest
import (
"fmt"
"sync"
"time"
"github.com/golang/glog"
rbacapiv1 "k8s.io/api/rbac/v1"
rbacapiv1alpha1 "k8s.io/api/rbac/v1alpha1"
rbacapiv1beta1 "k8s.io/api/rbac/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/client-go/util/retry"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/rbac"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
rbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
"k8s.io/kubernetes/pkg/registry/rbac/clusterrole"
clusterrolepolicybased "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/policybased"
clusterrolestore "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/storage"
"k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"
clusterrolebindingpolicybased "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/policybased"
clusterrolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/storage"
"k8s.io/kubernetes/pkg/registry/rbac/reconciliation"
"k8s.io/kubernetes/pkg/registry/rbac/role"
rolepolicybased "k8s.io/kubernetes/pkg/registry/rbac/role/policybased"
rolestore "k8s.io/kubernetes/pkg/registry/rbac/role/storage"
"k8s.io/kubernetes/pkg/registry/rbac/rolebinding"
rolebindingpolicybased "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/policybased"
rolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/storage"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
const PostStartHookName = "rbac/bootstrap-roles"
type RESTStorageProvider struct {
Authorizer authorizer.Authorizer
}
var _ genericapiserver.PostStartHookProvider = RESTStorageProvider{}
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(rbac.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
// If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities.
// TODO refactor the plumbing to provide the information in the APIGroupInfo
if apiResourceConfigSource.AnyResourcesForVersionEnabled(rbacapiv1alpha1.SchemeGroupVersion) {
apiGroupInfo.VersionedResourcesStorageMap[rbacapiv1alpha1.SchemeGroupVersion.Version] = p.storage(rbacapiv1alpha1.SchemeGroupVersion, apiResourceConfigSource, restOptionsGetter)
apiGroupInfo.GroupMeta.GroupVersion = rbacapiv1alpha1.SchemeGroupVersion
}
if apiResourceConfigSource.AnyResourcesForVersionEnabled(rbacapiv1beta1.SchemeGroupVersion) {
apiGroupInfo.VersionedResourcesStorageMap[rbacapiv1beta1.SchemeGroupVersion.Version] = p.storage(rbacapiv1beta1.SchemeGroupVersion, apiResourceConfigSource, restOptionsGetter)
apiGroupInfo.GroupMeta.GroupVersion = rbacapiv1beta1.SchemeGroupVersion
}
if apiResourceConfigSource.AnyResourcesForVersionEnabled(rbacapiv1.SchemeGroupVersion) {
apiGroupInfo.VersionedResourcesStorageMap[rbacapiv1.SchemeGroupVersion.Version] = p.storage(rbacapiv1.SchemeGroupVersion, apiResourceConfigSource, restOptionsGetter)
apiGroupInfo.GroupMeta.GroupVersion = rbacapiv1.SchemeGroupVersion
}
return apiGroupInfo, true
}
func (p RESTStorageProvider) storage(version schema.GroupVersion, apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
once := new(sync.Once)
var (
authorizationRuleResolver rbacregistryvalidation.AuthorizationRuleResolver
rolesStorage rest.StandardStorage
roleBindingsStorage rest.StandardStorage
clusterRolesStorage rest.StandardStorage
clusterRoleBindingsStorage rest.StandardStorage
)
initializeStorage := func() {
once.Do(func() {
rolesStorage = rolestore.NewREST(restOptionsGetter)
roleBindingsStorage = rolebindingstore.NewREST(restOptionsGetter)
clusterRolesStorage = clusterrolestore.NewREST(restOptionsGetter)
clusterRoleBindingsStorage = clusterrolebindingstore.NewREST(restOptionsGetter)
authorizationRuleResolver = rbacregistryvalidation.NewDefaultRuleResolver(
role.AuthorizerAdapter{Registry: role.NewRegistry(rolesStorage)},
rolebinding.AuthorizerAdapter{Registry: rolebinding.NewRegistry(roleBindingsStorage)},
clusterrole.AuthorizerAdapter{Registry: clusterrole.NewRegistry(clusterRolesStorage)},
clusterrolebinding.AuthorizerAdapter{Registry: clusterrolebinding.NewRegistry(clusterRoleBindingsStorage)},
)
})
}
storage := map[string]rest.Storage{}
if apiResourceConfigSource.ResourceEnabled(version.WithResource("roles")) {
initializeStorage()
storage["roles"] = rolepolicybased.NewStorage(rolesStorage, authorizationRuleResolver)
}
if apiResourceConfigSource.ResourceEnabled(version.WithResource("rolebindings")) {
initializeStorage()
storage["rolebindings"] = rolebindingpolicybased.NewStorage(roleBindingsStorage, p.Authorizer, authorizationRuleResolver)
}
if apiResourceConfigSource.ResourceEnabled(version.WithResource("clusterroles")) {
initializeStorage()
storage["clusterroles"] = clusterrolepolicybased.NewStorage(clusterRolesStorage, authorizationRuleResolver)
}
if apiResourceConfigSource.ResourceEnabled(version.WithResource("clusterrolebindings")) {
initializeStorage()
storage["clusterrolebindings"] = clusterrolebindingpolicybased.NewStorage(clusterRoleBindingsStorage, p.Authorizer, authorizationRuleResolver)
}
return storage
}
func (p RESTStorageProvider) PostStartHook() (string, genericapiserver.PostStartHookFunc, error) {
policy := &PolicyData{
ClusterRoles: append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...),
ClusterRoleBindings: append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...),
Roles: bootstrappolicy.NamespaceRoles(),
RoleBindings: bootstrappolicy.NamespaceRoleBindings(),
ClusterRolesToAggregate: bootstrappolicy.ClusterRolesToAggregate(),
}
return PostStartHookName, policy.EnsureRBACPolicy(), nil
}
type PolicyData struct {
ClusterRoles []rbac.ClusterRole
ClusterRoleBindings []rbac.ClusterRoleBinding
Roles map[string][]rbac.Role
RoleBindings map[string][]rbac.RoleBinding
// ClusterRolesToAggregate maps from previous clusterrole name to the new clusterrole name
ClusterRolesToAggregate map[string]string
}
func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc {
return func(hookContext genericapiserver.PostStartHookContext) error {
// intializing roles is really important. On some e2e runs, we've seen cases where etcd is down when the server
// starts, the roles don't initialize, and nothing works.
err := wait.Poll(1*time.Second, 30*time.Second, func() (done bool, err error) {
coreclientset, err := coreclient.NewForConfig(hookContext.LoopbackClientConfig)
if err != nil {
utilruntime.HandleError(fmt.Errorf("unable to initialize client: %v", err))
return false, nil
}
clientset, err := rbacclient.NewForConfig(hookContext.LoopbackClientConfig)
if err != nil {
utilruntime.HandleError(fmt.Errorf("unable to initialize client: %v", err))
return false, nil
}
// Make sure etcd is responding before we start reconciling
if _, err := clientset.ClusterRoles().List(metav1.ListOptions{}); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err))
return false, nil
}
if _, err := clientset.ClusterRoleBindings().List(metav1.ListOptions{}); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterrolebindings: %v", err))
return false, nil
}
// if the new cluster roles to aggregate do not yet exist, then we need to copy the old roles if they don't exist
// in new locations
if err := primeAggregatedClusterRoles(p.ClusterRolesToAggregate, clientset); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to prime aggregated clusterroles: %v", err))
return false, nil
}
// ensure bootstrap roles are created or reconciled
for _, clusterRole := range p.ClusterRoles {
opts := reconciliation.ReconcileRoleOptions{
Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: &clusterRole},
Client: reconciliation.ClusterRoleModifier{Client: clientset.ClusterRoles()},
Confirm: true,
}
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
result, err := opts.Run()
if err != nil {
return err
}
switch {
case result.Protected && result.Operation != reconciliation.ReconcileNone:
glog.Warningf("skipped reconcile-protected clusterrole.%s/%s with missing permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules)
case result.Operation == reconciliation.ReconcileUpdate:
glog.Infof("updated clusterrole.%s/%s with additional permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules)
case result.Operation == reconciliation.ReconcileCreate:
glog.Infof("created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name)
}
return nil
})
if err != nil {
// don't fail on failures, try to create as many as you can
utilruntime.HandleError(fmt.Errorf("unable to reconcile clusterrole.%s/%s: %v", rbac.GroupName, clusterRole.Name, err))
}
}
// ensure bootstrap rolebindings are created or reconciled
for _, clusterRoleBinding := range p.ClusterRoleBindings {
opts := reconciliation.ReconcileRoleBindingOptions{
RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: &clusterRoleBinding},
Client: reconciliation.ClusterRoleBindingClientAdapter{Client: clientset.ClusterRoleBindings()},
Confirm: true,
}
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
result, err := opts.Run()
if err != nil {
return err
}
switch {
case result.Protected && result.Operation != reconciliation.ReconcileNone:
glog.Warningf("skipped reconcile-protected clusterrolebinding.%s/%s with missing subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects)
case result.Operation == reconciliation.ReconcileUpdate:
glog.Infof("updated clusterrolebinding.%s/%s with additional subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects)
case result.Operation == reconciliation.ReconcileCreate:
glog.Infof("created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
case result.Operation == reconciliation.ReconcileRecreate:
glog.Infof("recreated clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
}
return nil
})
if err != nil {
// don't fail on failures, try to create as many as you can
utilruntime.HandleError(fmt.Errorf("unable to reconcile clusterrolebinding.%s/%s: %v", rbac.GroupName, clusterRoleBinding.Name, err))
}
}
// ensure bootstrap namespaced roles are created or reconciled
for namespace, roles := range p.Roles {
for _, role := range roles {
opts := reconciliation.ReconcileRoleOptions{
Role: reconciliation.RoleRuleOwner{Role: &role},
Client: reconciliation.RoleModifier{Client: clientset, NamespaceClient: coreclientset.Namespaces()},
Confirm: true,
}
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
result, err := opts.Run()
if err != nil {
return err
}
switch {
case result.Protected && result.Operation != reconciliation.ReconcileNone:
glog.Warningf("skipped reconcile-protected role.%s/%s in %v with missing permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules)
case result.Operation == reconciliation.ReconcileUpdate:
glog.Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules)
case result.Operation == reconciliation.ReconcileCreate:
glog.Infof("created role.%s/%s in %v ", rbac.GroupName, role.Name, namespace)
}
return nil
})
if err != nil {
// don't fail on failures, try to create as many as you can
utilruntime.HandleError(fmt.Errorf("unable to reconcile role.%s/%s in %v: %v", rbac.GroupName, role.Name, namespace, err))
}
}
}
// ensure bootstrap namespaced rolebindings are created or reconciled
for namespace, roleBindings := range p.RoleBindings {
for _, roleBinding := range roleBindings {
opts := reconciliation.ReconcileRoleBindingOptions{
RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: &roleBinding},
Client: reconciliation.RoleBindingClientAdapter{Client: clientset, NamespaceClient: coreclientset.Namespaces()},
Confirm: true,
}
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
result, err := opts.Run()
if err != nil {
return err
}
switch {
case result.Protected && result.Operation != reconciliation.ReconcileNone:
glog.Warningf("skipped reconcile-protected rolebinding.%s/%s in %v with missing subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects)
case result.Operation == reconciliation.ReconcileUpdate:
glog.Infof("updated rolebinding.%s/%s in %v with additional subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects)
case result.Operation == reconciliation.ReconcileCreate:
glog.Infof("created rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace)
case result.Operation == reconciliation.ReconcileRecreate:
glog.Infof("recreated rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace)
}
return nil
})
if err != nil {
// don't fail on failures, try to create as many as you can
utilruntime.HandleError(fmt.Errorf("unable to reconcile rolebinding.%s/%s in %v: %v", rbac.GroupName, roleBinding.Name, namespace, err))
}
}
}
return true, nil
})
// if we're never able to make it through intialization, kill the API server
if err != nil {
return fmt.Errorf("unable to initialize roles: %v", err)
}
return nil
}
}
func (p RESTStorageProvider) GroupName() string {
return rbac.GroupName
}
// primeAggregatedClusterRoles copies roles that have transitioned to aggregated roles and may need to pick up changes
// that were done to the legacy roles.
func primeAggregatedClusterRoles(clusterRolesToAggregate map[string]string, clusterRoleClient rbacclient.ClusterRolesGetter) error {
for oldName, newName := range clusterRolesToAggregate {
_, err := clusterRoleClient.ClusterRoles().Get(newName, metav1.GetOptions{})
if err == nil {
continue
}
if !apierrors.IsNotFound(err) {
return err
}
existingRole, err := clusterRoleClient.ClusterRoles().Get(oldName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
continue
}
if err != nil {
return err
}
glog.V(1).Infof("migrating %v to %v", existingRole.Name, newName)
existingRole.Name = newName
existingRole.ResourceVersion = "" // clear this so the object can be created.
if _, err := clusterRoleClient.ClusterRoles().Create(existingRole); err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
}
return nil
}

46
vendor/k8s.io/kubernetes/pkg/registry/rbac/role/BUILD generated vendored Normal file
View File

@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"registry.go",
"strategy.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/role",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/registry/rbac/role/policybased:all-srcs",
"//pkg/registry/rbac/role/storage:all-srcs",
],
tags = ["automanaged"],
)

19
vendor/k8s.io/kubernetes/pkg/registry/rbac/role/doc.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package certificates provides Registry interface and its RESTStorage
// implementation for storing Role objects.
package role // import "k8s.io/kubernetes/pkg/registry/rbac/role"

View File

@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/role/policybased",
deps = [
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,77 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package policybased implements a standard storage for Role that prevents privilege escalation.
package policybased
import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("roles")
type Storage struct {
rest.StandardStorage
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, ruleResolver}
}
func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
role := obj.(*rbac.Role)
rules := role.Rules
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, role.Name, err)
}
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation)
}
nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {
role := obj.(*rbac.Role)
// if we're only mutating fields needed for the GC to eventually delete this obj, return
if rbacregistry.IsOnlyMutatingGCFields(obj, oldObj, kapihelper.Semantic) {
return obj, nil
}
rules := role.Rules
if err := rbacregistryvalidation.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, createValidation, updateValidation)
}

View File

@ -0,0 +1,93 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package role
import (
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// Registry is an interface for things that know how to store Roles.
type Registry interface {
ListRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.RoleList, error)
CreateRole(ctx genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc) error
UpdateRole(ctx genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error
GetRole(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.Role, error)
DeleteRole(ctx genericapirequest.Context, name string) error
WatchRoles(ctx genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc) error {
_, err := s.Create(ctx, role, createValidation, false)
return err
}
func (s *storage) UpdateRole(ctx genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error {
// TODO: any admission?
_, _, err := s.Update(ctx, role.Name, rest.DefaultUpdatedObjectInfo(role), createValidation, updateValidation)
return err
}
func (s *storage) WatchRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
return s.Watch(ctx, options)
}
func (s *storage) GetRole(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.Role, error) {
obj, err := s.Get(ctx, name, options)
if err != nil {
return nil, err
}
return obj.(*rbac.Role), nil
}
func (s *storage) DeleteRole(ctx genericapirequest.Context, name string) error {
_, _, err := s.Delete(ctx, name, nil)
return err
}
// AuthorizerAdapter adapts the registry to the authorizer interface
type AuthorizerAdapter struct {
Registry Registry
}
func (a AuthorizerAdapter) GetRole(namespace, name string) (*rbac.Role, error) {
return a.Registry.GetRole(genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace), name, &metav1.GetOptions{})
}

View File

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/role/storage",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac/role:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,49 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storage
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/registry/rbac/role"
)
// REST implements a RESTStorage for Role
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against Role objects.
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &rbac.Role{} },
NewListFunc: func() runtime.Object { return &rbac.RoleList{} },
DefaultQualifiedResource: rbac.Resource("roles"),
CreateStrategy: role.Strategy,
UpdateStrategy: role.Strategy,
DeleteStrategy: role.Strategy,
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil {
panic(err) // TODO: Propagate error up
}
return &REST{store}
}

View File

@ -0,0 +1,95 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package role
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
)
// strategy implements behavior for Roles
type strategy struct {
runtime.ObjectTyper
names.NameGenerator
}
// strategy is the default logic that applies when creating and updating
// Role objects.
var Strategy = strategy{legacyscheme.Scheme, names.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(ctx genericapirequest.Context, obj runtime.Object) {
_ = obj.(*rbac.Role)
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (strategy) PrepareForUpdate(ctx genericapirequest.Context, 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 genericapirequest.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 genericapirequest.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
}

View File

@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"registry.go",
"strategy.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/rolebinding",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/registry/rbac/rolebinding/policybased:all-srcs",
"//pkg/registry/rbac/rolebinding/storage:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,19 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package certificates provides Registry interface and its RESTStorage
// implementation for storing RoleBinding objects.
package rolebinding // import "k8s.io/kubernetes/pkg/registry/rbac/rolebinding"

View File

@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/policybased",
deps = [
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,110 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package policybased implements a standard storage for RoleBinding that prevents privilege escalation.
package policybased
import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("rolebindings")
type Storage struct {
rest.StandardStorage
authorizer authorizer.Authorizer
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, authorizer, ruleResolver}
}
func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
// Get the namespace from the context (populated from the URL).
// The namespace in the object can be empty until StandardStorage.Create()->BeforeCreate() populates it from the context.
namespace, ok := genericapirequest.NamespaceFrom(ctx)
if !ok {
return nil, errors.NewBadRequest("namespace is required")
}
roleBinding := obj.(*rbac.RoleBinding)
if rbacregistry.BindingAuthorized(ctx, roleBinding.RoleRef, namespace, s.authorizer) {
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
rules, err := s.ruleResolver.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
return nil, err
}
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, roleBinding.Name, err)
}
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
if rbacregistry.EscalationAllowed(ctx) {
return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation)
}
nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {
// Get the namespace from the context (populated from the URL).
// The namespace in the object can be empty until StandardStorage.Update()->BeforeUpdate() populates it from the context.
namespace, ok := genericapirequest.NamespaceFrom(ctx)
if !ok {
return nil, errors.NewBadRequest("namespace is required")
}
roleBinding := obj.(*rbac.RoleBinding)
// if we're only mutating fields needed for the GC to eventually delete this obj, return
if rbacregistry.IsOnlyMutatingGCFields(obj, oldObj, kapihelper.Semantic) {
return obj, nil
}
// if we're explicitly authorized to bind this role, return
if rbacregistry.BindingAuthorized(ctx, roleBinding.RoleRef, namespace, s.authorizer) {
return obj, nil
}
// Otherwise, see if we already have all the permissions contained in the referenced role
rules, err := s.ruleResolver.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
return nil, err
}
if err := rbacregistryvalidation.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, createValidation, updateValidation)
}

View File

@ -0,0 +1,102 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rolebinding
import (
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// Registry is an interface for things that know how to store RoleBindings.
type Registry interface {
ListRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.RoleBindingList, error)
CreateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc) error
UpdateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error
GetRoleBinding(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.RoleBinding, error)
DeleteRoleBinding(ctx genericapirequest.Context, name string) error
WatchRoleBindings(ctx genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, options *metainternalversion.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 genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc) error {
// TODO(ericchiang): add additional validation
_, err := s.Create(ctx, roleBinding, createValidation, false)
return err
}
func (s *storage) UpdateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error {
_, _, err := s.Update(ctx, roleBinding.Name, rest.DefaultUpdatedObjectInfo(roleBinding), createValidation, updateValidation)
return err
}
func (s *storage) WatchRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
return s.Watch(ctx, options)
}
func (s *storage) GetRoleBinding(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.RoleBinding, error) {
obj, err := s.Get(ctx, name, options)
if err != nil {
return nil, err
}
return obj.(*rbac.RoleBinding), nil
}
func (s *storage) DeleteRoleBinding(ctx genericapirequest.Context, name string) error {
_, _, err := s.Delete(ctx, name, nil)
return err
}
// AuthorizerAdapter adapts the registry to the authorizer interface
type AuthorizerAdapter struct {
Registry Registry
}
func (a AuthorizerAdapter) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error) {
list, err := a.Registry.ListRoleBindings(genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace), &metainternalversion.ListOptions{})
if err != nil {
return nil, err
}
ret := []*rbac.RoleBinding{}
for i := range list.Items {
ret = append(ret, &list.Items[i])
}
return ret, nil
}

View File

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/storage",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac/rolebinding:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,49 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storage
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/registry/rbac/rolebinding"
)
// REST implements a RESTStorage for RoleBinding
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against RoleBinding objects.
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &rbac.RoleBinding{} },
NewListFunc: func() runtime.Object { return &rbac.RoleBindingList{} },
DefaultQualifiedResource: rbac.Resource("rolebindings"),
CreateStrategy: rolebinding.Strategy,
UpdateStrategy: rolebinding.Strategy,
DeleteStrategy: rolebinding.Strategy,
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil {
panic(err) // TODO: Propagate error up
}
return &REST{store}
}

View File

@ -0,0 +1,95 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rolebinding
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
)
// strategy implements behavior for RoleBindings
type strategy struct {
runtime.ObjectTyper
names.NameGenerator
}
// strategy is the default logic that applies when creating and updating
// RoleBinding objects.
var Strategy = strategy{legacyscheme.Scheme, names.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(ctx genericapirequest.Context, obj runtime.Object) {
_ = obj.(*rbac.RoleBinding)
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (strategy) PrepareForUpdate(ctx genericapirequest.Context, 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 genericapirequest.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 genericapirequest.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
}

View File

@ -0,0 +1,56 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"policy_compact_test.go",
"policy_comparator_test.go",
"rule_test.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/validation",
library = ":go_default_library",
deps = [
"//pkg/apis/rbac:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"policy_compact.go",
"policy_comparator.go",
"rule.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/validation",
deps = [
"//pkg/apis/rbac:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,89 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"reflect"
"k8s.io/kubernetes/pkg/apis/rbac"
)
type simpleResource struct {
Group string
Resource string
ResourceNameExist bool
ResourceName string
}
// CompactRules combines rules that contain a single APIGroup/Resource, differ only by verb, and contain no other attributes.
// this is a fast check, and works well with the decomposed "missing rules" list from a Covers check.
func CompactRules(rules []rbac.PolicyRule) ([]rbac.PolicyRule, error) {
compacted := make([]rbac.PolicyRule, 0, len(rules))
simpleRules := map[simpleResource]*rbac.PolicyRule{}
for _, rule := range rules {
if resource, isSimple := isSimpleResourceRule(&rule); isSimple {
if existingRule, ok := simpleRules[resource]; ok {
// Add the new verbs to the existing simple resource rule
if existingRule.Verbs == nil {
existingRule.Verbs = []string{}
}
existingRule.Verbs = append(existingRule.Verbs, rule.Verbs...)
} else {
// Copy the rule to accumulate matching simple resource rules into
simpleRules[resource] = rule.DeepCopy()
}
} else {
compacted = append(compacted, rule)
}
}
// Once we've consolidated the simple resource rules, add them to the compacted list
for _, simpleRule := range simpleRules {
compacted = append(compacted, *simpleRule)
}
return compacted, nil
}
// isSimpleResourceRule returns true if the given rule contains verbs, a single resource, a single API group, at most one Resource Name, and no other values
func isSimpleResourceRule(rule *rbac.PolicyRule) (simpleResource, bool) {
resource := simpleResource{}
// If we have "complex" rule attributes, return early without allocations or expensive comparisons
if len(rule.ResourceNames) > 1 || len(rule.NonResourceURLs) > 0 {
return resource, false
}
// If we have multiple api groups or resources, return early
if len(rule.APIGroups) != 1 || len(rule.Resources) != 1 {
return resource, false
}
// Test if this rule only contains APIGroups/Resources/Verbs/ResourceNames
simpleRule := &rbac.PolicyRule{APIGroups: rule.APIGroups, Resources: rule.Resources, Verbs: rule.Verbs, ResourceNames: rule.ResourceNames}
if !reflect.DeepEqual(simpleRule, rule) {
return resource, false
}
if len(rule.ResourceNames) == 0 {
resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: false}
} else {
resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: true, ResourceName: rule.ResourceNames[0]}
}
return resource, true
}

View File

@ -0,0 +1,226 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"reflect"
"sort"
"testing"
"k8s.io/kubernetes/pkg/apis/rbac"
)
func TestCompactRules(t *testing.T) {
testcases := map[string]struct {
Rules []rbac.PolicyRule
Expected []rbac.PolicyRule
}{
"empty": {
Rules: []rbac.PolicyRule{},
Expected: []rbac.PolicyRule{},
},
"simple": {
Rules: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"update", "patch"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"create"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}},
{Verbs: []string{"delete"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}},
{Verbs: []string{"patch"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{""}},
{Verbs: []string{"get"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{"foo"}},
{Verbs: []string{"list"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{"foo"}},
{Verbs: []string{"educate"}, APIGroups: []string{""}, Resources: []string{"dolphins"}},
// nil verbs are preserved in non-merge cases.
// these are the pirates who don't do anything.
{Verbs: nil, APIGroups: []string{""}, Resources: []string{"pirates"}},
// Test merging into a nil Verbs string set
{Verbs: nil, APIGroups: []string{""}, Resources: []string{"pods"}},
{Verbs: []string{"create"}, APIGroups: []string{""}, Resources: []string{"pods"}},
},
Expected: []rbac.PolicyRule{
{Verbs: []string{"create", "delete"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}},
{Verbs: []string{"patch"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{""}},
{Verbs: []string{"get", "list"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{"foo"}},
{Verbs: []string{"get", "list", "update", "patch"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"educate"}, APIGroups: []string{""}, Resources: []string{"dolphins"}},
{Verbs: nil, APIGroups: []string{""}, Resources: []string{"pirates"}},
{Verbs: []string{"create"}, APIGroups: []string{""}, Resources: []string{"pods"}},
},
},
"complex multi-group": {
Rules: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
{Verbs: []string{"list"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
},
Expected: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
{Verbs: []string{"list"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
},
},
"complex multi-resource": {
Rules: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
},
Expected: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
},
},
"complex named-resource": {
Rules: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild2"}},
},
Expected: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild2"}},
},
},
"complex non-resource": {
Rules: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/"}},
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/foo"}},
},
Expected: []rbac.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/"}},
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/foo"}},
},
},
}
for k, tc := range testcases {
rules := tc.Rules
originalRules := make([]rbac.PolicyRule, len(tc.Rules))
for i := range tc.Rules {
originalRules[i] = *tc.Rules[i].DeepCopy()
}
compacted, err := CompactRules(tc.Rules)
if err != nil {
t.Errorf("%s: unexpected error: %v", k, err)
continue
}
if !reflect.DeepEqual(rules, originalRules) {
t.Errorf("%s: CompactRules mutated rules. Expected\n%#v\ngot\n%#v", k, originalRules, rules)
continue
}
if covers, missing := Covers(compacted, rules); !covers {
t.Errorf("%s: compacted rules did not cover original rules. missing: %#v", k, missing)
continue
}
if covers, missing := Covers(rules, compacted); !covers {
t.Errorf("%s: original rules did not cover compacted rules. missing: %#v", k, missing)
continue
}
sort.Stable(rbac.SortableRuleSlice(compacted))
sort.Stable(rbac.SortableRuleSlice(tc.Expected))
if !reflect.DeepEqual(compacted, tc.Expected) {
t.Errorf("%s: Expected\n%#v\ngot\n%#v", k, tc.Expected, compacted)
continue
}
}
}
func TestIsSimpleResourceRule(t *testing.T) {
testcases := map[string]struct {
Rule rbac.PolicyRule
Simple bool
Resource simpleResource
}{
"simple, no verbs": {
Rule: rbac.PolicyRule{Verbs: []string{}, APIGroups: []string{""}, Resources: []string{"builds"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds"},
},
"simple, one verb": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds"},
},
"simple, one empty resource name": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{""}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds", ResourceNameExist: true, ResourceName: ""},
},
"simple, one resource name": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"foo"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds", ResourceNameExist: true, ResourceName: "foo"},
},
"simple, multi verb": {
Rule: rbac.PolicyRule{Verbs: []string{"get", "list"}, APIGroups: []string{""}, Resources: []string{"builds"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds"},
},
"complex, empty": {
Rule: rbac.PolicyRule{},
Simple: false,
Resource: simpleResource{},
},
"complex, no group": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{}, Resources: []string{"builds"}},
Simple: false,
Resource: simpleResource{},
},
"complex, multi group": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{"a", "b"}, Resources: []string{"builds"}},
Simple: false,
Resource: simpleResource{},
},
"complex, no resource": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{}},
Simple: false,
Resource: simpleResource{},
},
"complex, multi resource": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
Simple: false,
Resource: simpleResource{},
},
"complex, resource names": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"foo", "bar"}},
Simple: false,
Resource: simpleResource{},
},
"complex, non-resource urls": {
Rule: rbac.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/"}},
Simple: false,
Resource: simpleResource{},
},
}
for k, tc := range testcases {
resource, simple := isSimpleResourceRule(&tc.Rule)
if simple != tc.Simple {
t.Errorf("%s: expected simple=%v, got simple=%v", k, tc.Simple, simple)
continue
}
if resource != tc.Resource {
t.Errorf("%s: expected resource=%v, got resource=%v", k, tc.Resource, resource)
continue
}
}
}

View File

@ -0,0 +1,173 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"strings"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// Covers determines whether or not the ownerRules cover the servantRules in terms of allowed actions.
// It returns whether or not the ownerRules cover and a list of the rules that the ownerRules do not cover.
func Covers(ownerRules, servantRules []rbac.PolicyRule) (bool, []rbac.PolicyRule) {
// 1. Break every servantRule into individual rule tuples: group, verb, resource, resourceName
// 2. Compare the mini-rules against each owner rule. Because the breakdown is down to the most atomic level, we're guaranteed that each mini-servant rule will be either fully covered or not covered by a single owner rule
// 3. Any left over mini-rules means that we are not covered and we have a nice list of them.
// TODO: it might be nice to collapse the list down into something more human readable
subrules := []rbac.PolicyRule{}
for _, servantRule := range servantRules {
subrules = append(subrules, BreakdownRule(servantRule)...)
}
uncoveredRules := []rbac.PolicyRule{}
for _, subrule := range subrules {
covered := false
for _, ownerRule := range ownerRules {
if ruleCovers(ownerRule, subrule) {
covered = true
break
}
}
if !covered {
uncoveredRules = append(uncoveredRules, subrule)
}
}
return (len(uncoveredRules) == 0), uncoveredRules
}
// BreadownRule takes a rule and builds an equivalent list of rules that each have at most one verb, one
// resource, and one resource name
func BreakdownRule(rule rbac.PolicyRule) []rbac.PolicyRule {
subrules := []rbac.PolicyRule{}
for _, group := range rule.APIGroups {
for _, resource := range rule.Resources {
for _, verb := range rule.Verbs {
if len(rule.ResourceNames) > 0 {
for _, resourceName := range rule.ResourceNames {
subrules = append(subrules, rbac.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}, ResourceNames: []string{resourceName}})
}
} else {
subrules = append(subrules, rbac.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}})
}
}
}
}
// Non-resource URLs are unique because they only combine with verbs.
for _, nonResourceURL := range rule.NonResourceURLs {
for _, verb := range rule.Verbs {
subrules = append(subrules, rbac.PolicyRule{NonResourceURLs: []string{nonResourceURL}, Verbs: []string{verb}})
}
}
return subrules
}
func has(set []string, ele string) bool {
for _, s := range set {
if s == ele {
return true
}
}
return false
}
func hasAll(set, contains []string) bool {
owning := make(map[string]struct{}, len(set))
for _, ele := range set {
owning[ele] = struct{}{}
}
for _, ele := range contains {
if _, ok := owning[ele]; !ok {
return false
}
}
return true
}
func resourceCoversAll(setResources, coversResources []string) bool {
// if we have a star or an exact match on all resources, then we match
if has(setResources, rbac.ResourceAll) || hasAll(setResources, coversResources) {
return true
}
for _, path := range coversResources {
// if we have an exact match, then we match.
if has(setResources, path) {
continue
}
// if we're not a subresource, then we definitely don't match. fail.
if !strings.Contains(path, "/") {
return false
}
tokens := strings.SplitN(path, "/", 2)
resourceToCheck := "*/" + tokens[1]
if !has(setResources, resourceToCheck) {
return false
}
}
return true
}
func nonResourceURLsCoversAll(set, covers []string) bool {
for _, path := range covers {
covered := false
for _, owner := range set {
if nonResourceURLCovers(owner, path) {
covered = true
break
}
}
if !covered {
return false
}
}
return true
}
func nonResourceURLCovers(ownerPath, subPath string) bool {
if ownerPath == subPath {
return true
}
return strings.HasSuffix(ownerPath, "*") && strings.HasPrefix(subPath, strings.TrimRight(ownerPath, "*"))
}
// ruleCovers determines whether the ownerRule (which may have multiple verbs, resources, and resourceNames) covers
// the subrule (which may only contain at most one verb, resource, and resourceName)
func ruleCovers(ownerRule, subRule rbac.PolicyRule) bool {
verbMatches := has(ownerRule.Verbs, rbac.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs)
groupMatches := has(ownerRule.APIGroups, rbac.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups)
resourceMatches := resourceCoversAll(ownerRule.Resources, subRule.Resources)
nonResourceURLMatches := nonResourceURLsCoversAll(ownerRule.NonResourceURLs, subRule.NonResourceURLs)
resourceNameMatches := false
if len(subRule.ResourceNames) == 0 {
resourceNameMatches = (len(ownerRule.ResourceNames) == 0)
} else {
resourceNameMatches = (len(ownerRule.ResourceNames) == 0) || hasAll(ownerRule.ResourceNames, subRule.ResourceNames)
}
return verbMatches && groupMatches && resourceMatches && resourceNameMatches && nonResourceURLMatches
}

View File

@ -0,0 +1,446 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/apis/rbac"
)
type escalationTest struct {
ownerRules []rbac.PolicyRule
servantRules []rbac.PolicyRule
expectedCovered bool
expectedUncoveredRules []rbac.PolicyRule
}
func TestCoversExactMatch(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversSubresourceWildcard(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*/scale"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"foo/scale"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversMultipleRulesCoveringSingleRule(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"v1"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"v1"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversMultipleAPIGroupsCoveringSingleRule(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group1"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group2"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"group1", "group2"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversSingleAPIGroupsCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"group1", "group2"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group1"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group2"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversMultipleRulesMissingSingleVerbResourceCombination(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
{APIGroups: []string{"v1"}, Verbs: []string{"delete"}, Resources: []string{"pods"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments", "pods"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"update"}, Resources: []string{"pods"}},
},
}.test(t)
}
func TestCoversAPIGroupStarCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"group1", "group2"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringAPIGroupStar(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"dummy-group"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbac.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
}.test(t)
}
func TestCoversAPIGroupStarCoveringStar(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversVerbStarCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"watch", "list"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringVerbStar(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get", "list", "watch", "create", "update", "delete", "exec"}, Resources: []string{"roles"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
}.test(t)
}
func TestCoversVerbStarCoveringStar(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversResourceStarCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"resourcegroup:deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringResourceStar(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"roles", "resourcegroup:deployments"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
}.test(t)
}
func TestCoversResourceStarCoveringStar(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversResourceNameEmptyCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{"foo", "bar"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringResourceNameEmpty(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{"foo", "bar"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{}},
},
expectedCovered: false,
expectedUncoveredRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}},
},
}.test(t)
}
func TestCoversNonResourceURLs(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis"}, Verbs: []string{"*"}},
},
servantRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis"}, Verbs: []string{"*"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsStar(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"*"}, Verbs: []string{"*"}},
},
servantRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis", "/apis/v1", "/"}, Verbs: []string{"*"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsStarAfterPrefixDoesntCover(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis/*"}, Verbs: []string{"*"}},
},
servantRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis", "/apis/v1"}, Verbs: []string{"get"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis"}, Verbs: []string{"get"}},
},
}.test(t)
}
func TestCoversNonResourceURLsStarAfterPrefix(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis/*"}, Verbs: []string{"*"}},
},
servantRules: []rbac.PolicyRule{
{NonResourceURLs: []string{"/apis/v1/foo", "/apis/v1"}, Verbs: []string{"get"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsWithOtherFields(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}, NonResourceURLs: []string{"/apis"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}, NonResourceURLs: []string{"/apis"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbac.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsWithOtherFieldsFailure(t *testing.T) {
escalationTest{
ownerRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}},
},
servantRules: []rbac.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}, NonResourceURLs: []string{"/apis"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbac.PolicyRule{{NonResourceURLs: []string{"/apis"}, Verbs: []string{"get"}}},
}.test(t)
}
func (test escalationTest) test(t *testing.T) {
actualCovered, actualUncoveredRules := Covers(test.ownerRules, test.servantRules)
if actualCovered != test.expectedCovered {
t.Errorf("expected %v, but got %v", test.expectedCovered, actualCovered)
}
if !rulesMatch(test.expectedUncoveredRules, actualUncoveredRules) {
t.Errorf("expected %v, but got %v", test.expectedUncoveredRules, actualUncoveredRules)
}
}
func rulesMatch(expectedRules, actualRules []rbac.PolicyRule) bool {
if len(expectedRules) != len(actualRules) {
return false
}
for _, expectedRule := range expectedRules {
found := false
for _, actualRule := range actualRules {
if reflect.DeepEqual(expectedRule, actualRule) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func TestNonResourceURLCovers(t *testing.T) {
tests := []struct {
owner string
requested string
want bool
}{
{"*", "", true},
{"*", "/", true},
{"*", "/api", true},
{"/*", "", false},
{"/*", "/", true},
{"/*", "/foo", true},
{"/api", "/api", true},
{"/apis", "/api", false},
{"/api/v1", "/api", false},
{"/api/v1", "/api/v1", true},
{"/api/*", "/api/v1", true},
{"/api/*", "/api", false},
{"/api/*/*", "/api/v1", false},
{"/*/v1/*", "/api/v1", false},
}
for _, tc := range tests {
got := nonResourceURLCovers(tc.owner, tc.requested)
if got != tc.want {
t.Errorf("nonResourceURLCovers(%q, %q): want=(%t), got=(%t)", tc.owner, tc.requested, tc.want, got)
}
}
}

View File

@ -0,0 +1,288 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"errors"
"fmt"
"github.com/golang/glog"
apierrors "k8s.io/apimachinery/pkg/api/errors"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/apis/rbac"
)
type AuthorizationRuleResolver interface {
// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namepsace
// of the role binding, the empty string if a cluster role binding.
GetRoleReferenceRules(roleRef rbac.RoleRef, namespace string) ([]rbac.PolicyRule, error)
// RulesFor returns the list of rules that apply to a given user in a given namespace and error. If an error is returned, the slice of
// PolicyRules may not be complete, but it contains all retrievable rules. This is done because policy rules are purely additive and policy determinations
// can be made on the basis of those rules that are found.
RulesFor(user user.Info, namespace string) ([]rbac.PolicyRule, error)
// VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, and each error encountered resolving those rules.
// If visitor() returns false, visiting is short-circuited.
VisitRulesFor(user user.Info, namespace string, visitor func(rule *rbac.PolicyRule, err error) bool)
}
// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
func ConfirmNoEscalation(ctx genericapirequest.Context, ruleResolver AuthorizationRuleResolver, rules []rbac.PolicyRule) error {
ruleResolutionErrors := []error{}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return fmt.Errorf("no user on context")
}
namespace, _ := genericapirequest.NamespaceFrom(ctx)
ownerRules, err := ruleResolver.RulesFor(user, namespace)
if err != nil {
// As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
ruleResolutionErrors = append(ruleResolutionErrors, err)
}
ownerRightsCover, missingRights := Covers(ownerRules, rules)
if !ownerRightsCover {
return apierrors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v ruleResolutionErrors=%v", missingRights, user, ownerRules, ruleResolutionErrors))
}
return nil
}
type DefaultRuleResolver struct {
roleGetter RoleGetter
roleBindingLister RoleBindingLister
clusterRoleGetter ClusterRoleGetter
clusterRoleBindingLister ClusterRoleBindingLister
}
func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBindingLister, clusterRoleGetter ClusterRoleGetter, clusterRoleBindingLister ClusterRoleBindingLister) *DefaultRuleResolver {
return &DefaultRuleResolver{roleGetter, roleBindingLister, clusterRoleGetter, clusterRoleBindingLister}
}
type RoleGetter interface {
GetRole(namespace, name string) (*rbac.Role, error)
}
type RoleBindingLister interface {
ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error)
}
type ClusterRoleGetter interface {
GetClusterRole(name string) (*rbac.ClusterRole, error)
}
type ClusterRoleBindingLister interface {
ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error)
}
func (r *DefaultRuleResolver) RulesFor(user user.Info, namespace string) ([]rbac.PolicyRule, error) {
visitor := &ruleAccumulator{}
r.VisitRulesFor(user, namespace, visitor.visit)
return visitor.rules, utilerrors.NewAggregate(visitor.errors)
}
type ruleAccumulator struct {
rules []rbac.PolicyRule
errors []error
}
func (r *ruleAccumulator) visit(rule *rbac.PolicyRule, err error) bool {
if rule != nil {
r.rules = append(r.rules, *rule)
}
if err != nil {
r.errors = append(r.errors, err)
}
return true
}
func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(rule *rbac.PolicyRule, err error) bool) {
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
if !visitor(nil, err) {
return
}
} else {
for _, clusterRoleBinding := range clusterRoleBindings {
if !appliesTo(user, clusterRoleBinding.Subjects, "") {
continue
}
rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
if err != nil {
if !visitor(nil, err) {
return
}
continue
}
for i := range rules {
if !visitor(&rules[i], nil) {
return
}
}
}
}
if len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
if !visitor(nil, err) {
return
}
} else {
for _, roleBinding := range roleBindings {
if !appliesTo(user, roleBinding.Subjects, namespace) {
continue
}
rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
if !visitor(nil, err) {
return
}
continue
}
for i := range rules {
if !visitor(&rules[i], nil) {
return
}
}
}
}
}
}
// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
func (r *DefaultRuleResolver) GetRoleReferenceRules(roleRef rbac.RoleRef, bindingNamespace string) ([]rbac.PolicyRule, error) {
switch kind := rbac.RoleRefGroupKind(roleRef); kind {
case rbac.Kind("Role"):
role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name)
if err != nil {
return nil, err
}
return role.Rules, nil
case rbac.Kind("ClusterRole"):
clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name)
if err != nil {
return nil, err
}
return clusterRole.Rules, nil
default:
return nil, fmt.Errorf("unsupported role reference kind: %q", kind)
}
}
func appliesTo(user user.Info, bindingSubjects []rbac.Subject, namespace string) bool {
for _, bindingSubject := range bindingSubjects {
if appliesToUser(user, bindingSubject, namespace) {
return true
}
}
return false
}
func appliesToUser(user user.Info, subject rbac.Subject, namespace string) bool {
switch subject.Kind {
case rbac.UserKind:
return user.GetName() == subject.Name
case rbac.GroupKind:
return has(user.GetGroups(), subject.Name)
case rbac.ServiceAccountKind:
// default the namespace to namespace we're working in if its available. This allows rolebindings that reference
// SAs in th local namespace to avoid having to qualify them.
saNamespace := namespace
if len(subject.Namespace) > 0 {
saNamespace = subject.Namespace
}
if len(saNamespace) == 0 {
return false
}
return serviceaccount.MakeUsername(saNamespace, subject.Name) == user.GetName()
default:
return false
}
}
// NewTestRuleResolver returns a rule resolver from lists of role objects.
func NewTestRuleResolver(roles []*rbac.Role, roleBindings []*rbac.RoleBinding, clusterRoles []*rbac.ClusterRole, clusterRoleBindings []*rbac.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) {
r := StaticRoles{
roles: roles,
roleBindings: roleBindings,
clusterRoles: clusterRoles,
clusterRoleBindings: clusterRoleBindings,
}
return newMockRuleResolver(&r), &r
}
func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver {
return NewDefaultRuleResolver(r, r, r, r)
}
// StaticRoles is a rule resolver that resolves from lists of role objects.
type StaticRoles struct {
roles []*rbac.Role
roleBindings []*rbac.RoleBinding
clusterRoles []*rbac.ClusterRole
clusterRoleBindings []*rbac.ClusterRoleBinding
}
func (r *StaticRoles) GetRole(namespace, name string) (*rbac.Role, error) {
if len(namespace) == 0 {
return nil, errors.New("must provide namespace when getting role")
}
for _, role := range r.roles {
if role.Namespace == namespace && role.Name == name {
return role, nil
}
}
return nil, errors.New("role not found")
}
func (r *StaticRoles) GetClusterRole(name string) (*rbac.ClusterRole, error) {
for _, clusterRole := range r.clusterRoles {
if clusterRole.Name == name {
return clusterRole, nil
}
}
return nil, errors.New("role not found")
}
func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error) {
if len(namespace) == 0 {
return nil, errors.New("must provide namespace when listing role bindings")
}
roleBindingList := []*rbac.RoleBinding{}
for _, roleBinding := range r.roleBindings {
if roleBinding.Namespace != namespace {
continue
}
// TODO(ericchiang): need to implement label selectors?
roleBindingList = append(roleBindingList, roleBinding)
}
return roleBindingList, nil
}
func (r *StaticRoles) ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error) {
return r.clusterRoleBindings, nil
}

View File

@ -0,0 +1,266 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"hash/fnv"
"io"
"reflect"
"sort"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// compute a hash of a policy rule so we can sort in a deterministic order
func hashOf(p rbac.PolicyRule) string {
hash := fnv.New32()
writeStrings := func(slis ...[]string) {
for _, sli := range slis {
for _, s := range sli {
io.WriteString(hash, s)
}
}
}
writeStrings(p.Verbs, p.APIGroups, p.Resources, p.ResourceNames, p.NonResourceURLs)
return string(hash.Sum(nil))
}
// byHash sorts a set of policy rules by a hash of its fields
type byHash []rbac.PolicyRule
func (b byHash) Len() int { return len(b) }
func (b byHash) Less(i, j int) bool { return hashOf(b[i]) < hashOf(b[j]) }
func (b byHash) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func TestDefaultRuleResolver(t *testing.T) {
ruleReadPods := rbac.PolicyRule{
Verbs: []string{"GET", "WATCH"},
APIGroups: []string{"v1"},
Resources: []string{"pods"},
}
ruleReadServices := rbac.PolicyRule{
Verbs: []string{"GET", "WATCH"},
APIGroups: []string{"v1"},
Resources: []string{"services"},
}
ruleWriteNodes := rbac.PolicyRule{
Verbs: []string{"PUT", "CREATE", "UPDATE"},
APIGroups: []string{"v1"},
Resources: []string{"nodes"},
}
ruleAdmin := rbac.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
}
staticRoles1 := StaticRoles{
roles: []*rbac.Role{
{
ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "readthings"},
Rules: []rbac.PolicyRule{ruleReadPods, ruleReadServices},
},
},
clusterRoles: []*rbac.ClusterRole{
{
ObjectMeta: metav1.ObjectMeta{Name: "cluster-admin"},
Rules: []rbac.PolicyRule{ruleAdmin},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "write-nodes"},
Rules: []rbac.PolicyRule{ruleWriteNodes},
},
},
roleBindings: []*rbac.RoleBinding{
{
ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1"},
Subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "foobar"},
{Kind: rbac.GroupKind, Name: "group1"},
},
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "readthings"},
},
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
{
Subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "admin"},
{Kind: rbac.GroupKind, Name: "admin"},
},
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: "cluster-admin"},
},
},
}
tests := []struct {
StaticRoles
// For a given context, what are the rules that apply?
user user.Info
namespace string
effectiveRules []rbac.PolicyRule
}{
{
StaticRoles: staticRoles1,
user: &user.DefaultInfo{Name: "foobar"},
namespace: "namespace1",
effectiveRules: []rbac.PolicyRule{ruleReadPods, ruleReadServices},
},
{
StaticRoles: staticRoles1,
user: &user.DefaultInfo{Name: "foobar"},
namespace: "namespace2",
effectiveRules: nil,
},
{
StaticRoles: staticRoles1,
// Same as above but without a namespace. Only cluster rules should apply.
user: &user.DefaultInfo{Name: "foobar", Groups: []string{"admin"}},
effectiveRules: []rbac.PolicyRule{ruleAdmin},
},
{
StaticRoles: staticRoles1,
user: &user.DefaultInfo{},
effectiveRules: nil,
},
}
for i, tc := range tests {
ruleResolver := newMockRuleResolver(&tc.StaticRoles)
rules, err := ruleResolver.RulesFor(tc.user, tc.namespace)
if err != nil {
t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err)
continue
}
// Sort for deep equals
sort.Sort(byHash(rules))
sort.Sort(byHash(tc.effectiveRules))
if !reflect.DeepEqual(rules, tc.effectiveRules) {
ruleDiff := diff.ObjectDiff(rules, tc.effectiveRules)
t.Errorf("case %d: %s", i, ruleDiff)
}
}
}
func TestAppliesTo(t *testing.T) {
tests := []struct {
subjects []rbac.Subject
user user.Info
namespace string
appliesTo bool
testCase string
}{
{
subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "foobar"},
appliesTo: true,
testCase: "single subject that matches username",
},
{
subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "barfoo"},
{Kind: rbac.UserKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "foobar"},
appliesTo: true,
testCase: "multiple subjects, one that matches username",
},
{
subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "barfoo"},
{Kind: rbac.UserKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "zimzam"},
appliesTo: false,
testCase: "multiple subjects, none that match username",
},
{
subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "barfoo"},
{Kind: rbac.GroupKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
appliesTo: true,
testCase: "multiple subjects, one that match group",
},
{
subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "barfoo"},
{Kind: rbac.GroupKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
namespace: "namespace1",
appliesTo: true,
testCase: "multiple subjects, one that match group, should ignore namespace",
},
{
subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "barfoo"},
{Kind: rbac.GroupKind, Name: "foobar"},
{Kind: rbac.ServiceAccountKind, Namespace: "kube-system", Name: "default"},
},
user: &user.DefaultInfo{Name: "system:serviceaccount:kube-system:default"},
namespace: "default",
appliesTo: true,
testCase: "multiple subjects with a service account that matches",
},
{
subjects: []rbac.Subject{
{Kind: rbac.UserKind, Name: "*"},
},
user: &user.DefaultInfo{Name: "foobar"},
namespace: "default",
appliesTo: false,
testCase: "* user subject name doesn't match all users",
},
{
subjects: []rbac.Subject{
{Kind: rbac.GroupKind, Name: user.AllAuthenticated},
{Kind: rbac.GroupKind, Name: user.AllUnauthenticated},
},
user: &user.DefaultInfo{Name: "foobar", Groups: []string{user.AllAuthenticated}},
namespace: "default",
appliesTo: true,
testCase: "binding to all authenticated and unauthenticated subjects matches authenticated user",
},
{
subjects: []rbac.Subject{
{Kind: rbac.GroupKind, Name: user.AllAuthenticated},
{Kind: rbac.GroupKind, Name: user.AllUnauthenticated},
},
user: &user.DefaultInfo{Name: "system:anonymous", Groups: []string{user.AllUnauthenticated}},
namespace: "default",
appliesTo: true,
testCase: "binding to all authenticated and unauthenticated subjects matches anonymous user",
},
}
for _, tc := range tests {
got := appliesTo(tc.user, tc.subjects, tc.namespace)
if got != tc.appliesTo {
t.Errorf("case %q want appliesTo=%t, got appliesTo=%t", tc.testCase, tc.appliesTo, got)
}
}
}