Allow non-RBAC authorizers to participate in role/clusterrole escalation checks

pull/8/head
Jordan Liggitt 2018-05-02 12:46:42 -04:00
parent f54593b740
commit 1034efd439
No known key found for this signature in database
GPG Key ID: 39928704103C7229
11 changed files with 514 additions and 12 deletions

View File

@ -311,7 +311,6 @@ pkg/registry/networking/rest
pkg/registry/policy/poddisruptionbudget
pkg/registry/policy/poddisruptionbudget/storage
pkg/registry/policy/rest
pkg/registry/rbac
pkg/registry/rbac/clusterrole
pkg/registry/rbac/clusterrole/policybased
pkg/registry/rbac/clusterrolebinding

View File

@ -54,7 +54,7 @@ var (
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status`))
// Valid resource verb list for validation.
validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "impersonate"}
validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "escalate", "impersonate"}
// Specialized verbs and GroupResources
specialVerbs = map[string][]schema.GroupResource{
@ -74,6 +74,16 @@ var (
Resource: "clusterroles",
},
},
"escalate": {
{
Group: "rbac.authorization.k8s.io",
Resource: "roles",
},
{
Group: "rbac.authorization.k8s.io",
Resource: "clusterroles",
},
},
"impersonate": {
{
Group: "",

View File

@ -18,6 +18,7 @@ go_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/runtime/schema: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",

View File

@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
@ -16,6 +17,7 @@ go_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/registry/rest:go_default_library",
],
)
@ -32,3 +34,22 @@ filegroup(
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["storage_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/install:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/k8s.io/api/rbac/v1: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/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",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
],
)

View File

@ -23,6 +23,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
@ -35,11 +36,13 @@ var groupResource = rbac.Resource("clusterroles")
type Storage struct {
rest.StandardStorage
authorizer authorizer.Authorizer
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, ruleResolver}
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, authorizer, ruleResolver}
}
func (r *Storage) NamespaceScoped() bool {
@ -52,7 +55,7 @@ var fullAuthority = []rbac.PolicyRule{
}
func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
if rbacregistry.EscalationAllowed(ctx) {
if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, s.authorizer) {
return s.StandardStorage.Create(ctx, obj, createValidatingAdmission, includeUninitialized)
}
@ -72,7 +75,7 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati
}
func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
if rbacregistry.EscalationAllowed(ctx) {
if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, s.authorizer) {
return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation)
}

View File

@ -0,0 +1,195 @@
/*
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.
*/
package policybased
import (
"context"
"testing"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
"k8s.io/kubernetes/pkg/registry/rbac/validation"
)
func TestEscalation(t *testing.T) {
createContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), ""), &request.RequestInfo{
IsResourceRequest: true,
Verb: "create",
APIGroup: "rbac.authorization.k8s.io",
APIVersion: "v1",
Resource: "clusterroles",
Name: "",
})
updateContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), ""), &request.RequestInfo{
IsResourceRequest: true,
Verb: "update",
APIGroup: "rbac.authorization.k8s.io",
APIVersion: "v1",
Resource: "clusterroles",
Name: "myrole",
})
superuser := &user.DefaultInfo{Name: "superuser", Groups: []string{"system:masters"}}
bob := &user.DefaultInfo{Name: "bob"}
steve := &user.DefaultInfo{Name: "steve"}
alice := &user.DefaultInfo{Name: "alice"}
authzCalled := 0
fakeStorage := &fakeStorage{}
fakeAuthorizer := authorizer.AuthorizerFunc(func(attr authorizer.Attributes) (authorizer.Decision, string, error) {
authzCalled++
if attr.GetUser().GetName() == "steve" {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "", nil
})
fakeRuleResolver, _ := validation.NewTestRuleResolver(
nil,
nil,
[]*rbacv1.ClusterRole{{ObjectMeta: metav1.ObjectMeta{Name: "alice-role"}, Rules: []rbacv1.PolicyRule{{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}}}}},
[]*rbacv1.ClusterRoleBinding{{RoleRef: rbacv1.RoleRef{Name: "alice-role", APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole"}, Subjects: []rbacv1.Subject{{Name: "alice", Kind: "User", APIGroup: "rbac.authorization.k8s.io"}}}},
)
role := &rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "myrole", Namespace: ""},
Rules: []rbac.PolicyRule{{APIGroups: []string{""}, Verbs: []string{"get"}, Resources: []string{"pods"}}},
}
s := NewStorage(fakeStorage, fakeAuthorizer, fakeRuleResolver)
testcases := []struct {
name string
user user.Info
expectAllowed bool
expectAuthz bool
}{
// superuser doesn't even trigger an authz check, and is allowed
{
name: "superuser",
user: superuser,
expectAuthz: false,
expectAllowed: true,
},
// bob triggers an authz check, is disallowed by the authorizer, and has no RBAC permissions, so is not allowed
{
name: "bob",
user: bob,
expectAuthz: true,
expectAllowed: false,
},
// steve triggers an authz check, is allowed by the authorizer, and has no RBAC permissions, but is still allowed
{
name: "steve",
user: steve,
expectAuthz: true,
expectAllowed: true,
},
// alice triggers an authz check, is denied by the authorizer, but has RBAC permissions in the fakeRuleResolver, so is allowed
{
name: "alice",
user: alice,
expectAuthz: true,
expectAllowed: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
_, err := s.Create(request.WithUser(createContext, tc.user), role, nil, false)
if tc.expectAllowed {
if err != nil {
t.Error(err)
return
}
if fakeStorage.created != 1 {
t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
return
}
} else {
if !errors.IsForbidden(err) {
t.Errorf("expected forbidden, got %v", err)
return
}
if fakeStorage.created != 0 {
t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
return
}
}
if tc.expectAuthz != (authzCalled > 0) {
t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
}
authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
_, _, err = s.Update(request.WithUser(updateContext, tc.user), role.Name, rest.DefaultUpdatedObjectInfo(role), nil, nil)
if tc.expectAllowed {
if err != nil {
t.Error(err)
return
}
if fakeStorage.updated != 1 {
t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
return
}
} else {
if !errors.IsForbidden(err) {
t.Errorf("expected forbidden, got %v", err)
return
}
if fakeStorage.updated != 0 {
t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
return
}
}
if tc.expectAuthz != (authzCalled > 0) {
t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
}
})
}
}
type fakeStorage struct {
updated int
created int
rest.StandardStorage
}
func (f *fakeStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
f.created++
return nil, nil
}
func (f *fakeStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
obj, err := objInfo.UpdatedObject(ctx, &rbac.ClusterRole{})
if err != nil {
return obj, false, err
}
f.updated++
return nil, false, nil
}

View File

@ -20,6 +20,7 @@ import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
@ -27,6 +28,7 @@ import (
"k8s.io/kubernetes/pkg/apis/rbac"
)
// EscalationAllowed checks if the user associated with the context is a superuser
func EscalationAllowed(ctx context.Context) bool {
u, ok := genericapirequest.UserFrom(ctx)
if !ok {
@ -44,6 +46,56 @@ func EscalationAllowed(ctx context.Context) bool {
return false
}
var roleResources = map[schema.GroupResource]bool{
rbac.SchemeGroupVersion.WithResource("clusterroles").GroupResource(): true,
rbac.SchemeGroupVersion.WithResource("roles").GroupResource(): true,
}
// RoleEscalationAuthorized checks if the user associated with the context is explicitly authorized to escalate the role resource associated with the context
func RoleEscalationAuthorized(ctx context.Context, a authorizer.Authorizer) bool {
if a == nil {
return false
}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return false
}
requestInfo, ok := genericapirequest.RequestInfoFrom(ctx)
if !ok {
return false
}
if !requestInfo.IsResourceRequest {
return false
}
requestResource := schema.GroupResource{Group: requestInfo.APIGroup, Resource: requestInfo.Resource}
if !roleResources[requestResource] {
return false
}
attrs := authorizer.AttributesRecord{
User: user,
Verb: "escalate",
APIGroup: requestInfo.APIGroup,
Resource: requestInfo.Resource,
Name: requestInfo.Name,
Namespace: requestInfo.Namespace,
ResourceRequest: true,
}
decision, _, err := a.Authorize(attrs)
if err != nil {
utilruntime.HandleError(fmt.Errorf(
"error authorizing user %#v to escalate %#v named %q in namespace %q: %v",
user, requestResource, requestInfo.Name, requestInfo.Namespace, err,
))
}
return decision == authorizer.DecisionAllow
}
// BindingAuthorized returns true if the user associated with the context is explicitly authorized to bind the specified roleRef
func BindingAuthorized(ctx context.Context, roleRef rbac.RoleRef, bindingNamespace string, a authorizer.Authorizer) bool {
if a == nil {

View File

@ -98,13 +98,13 @@ func (p RESTStorageProvider) storage(version schema.GroupVersion, apiResourceCon
)
// roles
storage["roles"] = rolepolicybased.NewStorage(rolesStorage, authorizationRuleResolver)
storage["roles"] = rolepolicybased.NewStorage(rolesStorage, p.Authorizer, authorizationRuleResolver)
// rolebindings
storage["rolebindings"] = rolebindingpolicybased.NewStorage(roleBindingsStorage, p.Authorizer, authorizationRuleResolver)
// clusterroles
storage["clusterroles"] = clusterrolepolicybased.NewStorage(clusterRolesStorage, authorizationRuleResolver)
storage["clusterroles"] = clusterrolepolicybased.NewStorage(clusterRolesStorage, p.Authorizer, authorizationRuleResolver)
// clusterrolebindings
storage["clusterrolebindings"] = clusterrolebindingpolicybased.NewStorage(clusterRoleBindingsStorage, p.Authorizer, authorizationRuleResolver)

View File

@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
@ -16,6 +17,7 @@ go_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/registry/rest:go_default_library",
],
)
@ -32,3 +34,22 @@ filegroup(
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["storage_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/install:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/k8s.io/api/rbac/v1: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/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",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
],
)

View File

@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/rbac"
@ -34,11 +35,13 @@ var groupResource = rbac.Resource("roles")
type Storage struct {
rest.StandardStorage
authorizer authorizer.Authorizer
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, ruleResolver}
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, authorizer, ruleResolver}
}
func (r *Storage) NamespaceScoped() bool {
@ -46,7 +49,7 @@ func (r *Storage) NamespaceScoped() bool {
}
func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
if rbacregistry.EscalationAllowed(ctx) {
if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, s.authorizer) {
return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized)
}
@ -59,7 +62,7 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati
}
func (s *Storage) Update(ctx context.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
if rbacregistry.EscalationAllowed(ctx) {
if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, s.authorizer) {
return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation)
}

View File

@ -0,0 +1,197 @@
/*
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.
*/
package policybased
import (
"context"
"testing"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
"k8s.io/kubernetes/pkg/registry/rbac/validation"
)
func TestEscalation(t *testing.T) {
createContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), "myns"), &request.RequestInfo{
IsResourceRequest: true,
Verb: "create",
APIGroup: "rbac.authorization.k8s.io",
APIVersion: "v1",
Namespace: "myns",
Resource: "roles",
Name: "",
})
updateContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), "myns"), &request.RequestInfo{
IsResourceRequest: true,
Verb: "update",
APIGroup: "rbac.authorization.k8s.io",
APIVersion: "v1",
Namespace: "myns",
Resource: "roles",
Name: "myrole",
})
superuser := &user.DefaultInfo{Name: "superuser", Groups: []string{"system:masters"}}
bob := &user.DefaultInfo{Name: "bob"}
steve := &user.DefaultInfo{Name: "steve"}
alice := &user.DefaultInfo{Name: "alice"}
authzCalled := 0
fakeStorage := &fakeStorage{}
fakeAuthorizer := authorizer.AuthorizerFunc(func(attr authorizer.Attributes) (authorizer.Decision, string, error) {
authzCalled++
if attr.GetUser().GetName() == "steve" {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "", nil
})
fakeRuleResolver, _ := validation.NewTestRuleResolver(
nil,
nil,
[]*rbacv1.ClusterRole{{ObjectMeta: metav1.ObjectMeta{Name: "alice-role"}, Rules: []rbacv1.PolicyRule{{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}}}}},
[]*rbacv1.ClusterRoleBinding{{RoleRef: rbacv1.RoleRef{Name: "alice-role", APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole"}, Subjects: []rbacv1.Subject{{Name: "alice", Kind: "User", APIGroup: "rbac.authorization.k8s.io"}}}},
)
role := &rbac.Role{
ObjectMeta: metav1.ObjectMeta{Name: "myrole", Namespace: "myns"},
Rules: []rbac.PolicyRule{{APIGroups: []string{""}, Verbs: []string{"get"}, Resources: []string{"pods"}}},
}
s := NewStorage(fakeStorage, fakeAuthorizer, fakeRuleResolver)
testcases := []struct {
name string
user user.Info
expectAllowed bool
expectAuthz bool
}{
// superuser doesn't even trigger an authz check, and is allowed
{
name: "superuser",
user: superuser,
expectAuthz: false,
expectAllowed: true,
},
// bob triggers an authz check, is disallowed by the authorizer, and has no RBAC permissions, so is not allowed
{
name: "bob",
user: bob,
expectAuthz: true,
expectAllowed: false,
},
// steve triggers an authz check, is allowed by the authorizer, and has no RBAC permissions, but is still allowed
{
name: "steve",
user: steve,
expectAuthz: true,
expectAllowed: true,
},
// alice triggers an authz check, is denied by the authorizer, but has RBAC permissions in the fakeRuleResolver, so is allowed
{
name: "alice",
user: alice,
expectAuthz: true,
expectAllowed: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
_, err := s.Create(request.WithUser(createContext, tc.user), role, nil, false)
if tc.expectAllowed {
if err != nil {
t.Error(err)
return
}
if fakeStorage.created != 1 {
t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
return
}
} else {
if !errors.IsForbidden(err) {
t.Errorf("expected forbidden, got %v", err)
return
}
if fakeStorage.created != 0 {
t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
return
}
}
if tc.expectAuthz != (authzCalled > 0) {
t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
}
authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
_, _, err = s.Update(request.WithUser(updateContext, tc.user), role.Name, rest.DefaultUpdatedObjectInfo(role), nil, nil)
if tc.expectAllowed {
if err != nil {
t.Error(err)
return
}
if fakeStorage.updated != 1 {
t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
return
}
} else {
if !errors.IsForbidden(err) {
t.Errorf("expected forbidden, got %v", err)
return
}
if fakeStorage.updated != 0 {
t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
return
}
}
if tc.expectAuthz != (authzCalled > 0) {
t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
}
})
}
}
type fakeStorage struct {
updated int
created int
rest.StandardStorage
}
func (f *fakeStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
f.created++
return nil, nil
}
func (f *fakeStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
obj, err := objInfo.UpdatedObject(ctx, &rbac.Role{})
if err != nil {
return obj, false, err
}
f.updated++
return nil, false, nil
}