diff --git a/pkg/apis/authorization/types.go b/pkg/apis/authorization/types.go index 37f271f8b4f..545d1fa82ac 100644 --- a/pkg/apis/authorization/types.go +++ b/pkg/apis/authorization/types.go @@ -56,6 +56,9 @@ type SelfSubjectAccessReview struct { Status SubjectAccessReviewStatus } +// +genclient=true +// +noMethods=true + // LocalSubjectAccessReview checks whether or not a user or group can perform an action in a given namespace. // Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions // checking. diff --git a/pkg/apis/authorization/v1beta1/types.go b/pkg/apis/authorization/v1beta1/types.go index 2cdc69dc57e..67a23eec554 100644 --- a/pkg/apis/authorization/v1beta1/types.go +++ b/pkg/apis/authorization/v1beta1/types.go @@ -57,6 +57,9 @@ type SelfSubjectAccessReview struct { Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } +// +genclient=true +// +noMethods=true + // LocalSubjectAccessReview checks whether or not a user or group can perform an action in a given namespace. // Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions // checking. diff --git a/pkg/apis/authorization/validation/validation.go b/pkg/apis/authorization/validation/validation.go index 9b6298872c3..73326ee9a72 100644 --- a/pkg/apis/authorization/validation/validation.go +++ b/pkg/apis/authorization/validation/validation.go @@ -67,8 +67,19 @@ func ValidateSelfSubjectAccessReview(sar *authorizationapi.SelfSubjectAccessRevi func ValidateLocalSubjectAccessReview(sar *authorizationapi.LocalSubjectAccessReview) field.ErrorList { allErrs := ValidateSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec")) - if !api.Semantic.DeepEqual(api.ObjectMeta{}, sar.ObjectMeta) { - allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty`)) + + objectMetaShallowCopy := sar.ObjectMeta + objectMetaShallowCopy.Namespace = "" + if !api.Semantic.DeepEqual(api.ObjectMeta{}, objectMetaShallowCopy) { + allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty except for namespace`)) } + + if sar.Spec.ResourceAttributes != nil && sar.Spec.ResourceAttributes.Namespace != sar.Namespace { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec.resourceAttributes.namespace"), sar.Spec.ResourceAttributes.Namespace, `must match metadata.namespace`)) + } + if sar.Spec.NonResourceAttributes != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec.nonResourceAttributes"), sar.Spec.NonResourceAttributes, `disallowed on this kind of request`)) + } + return allErrs } diff --git a/pkg/apis/authorization/validation/validation_test.go b/pkg/apis/authorization/validation/validation_test.go index ecc382037ea..833b3eb4c1b 100644 --- a/pkg/apis/authorization/validation/validation_test.go +++ b/pkg/apis/authorization/validation/validation_test.go @@ -20,6 +20,7 @@ import ( "strings" "testing" + "k8s.io/kubernetes/pkg/api" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" "k8s.io/kubernetes/pkg/util/validation/field" ) @@ -133,3 +134,68 @@ func TestValidateSelfSAR(t *testing.T) { } } } + +func TestValidateLocalSAR(t *testing.T) { + successCases := []authorizationapi.LocalSubjectAccessReview{ + { + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + User: "user", + }, + }, + } + for _, successCase := range successCases { + if errs := ValidateLocalSubjectAccessReview(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + name string + obj *authorizationapi.LocalSubjectAccessReview + msg string + }{ + { + name: "name", + obj: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: api.ObjectMeta{Name: "a"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + User: "user", + }, + }, + msg: "must be empty except for namespace", + }, + { + name: "namespace conflict", + obj: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: api.ObjectMeta{Namespace: "a"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + User: "user", + }, + }, + msg: "must match metadata.namespace", + }, + { + name: "nonresource", + obj: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: api.ObjectMeta{Namespace: "a"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + NonResourceAttributes: &authorizationapi.NonResourceAttributes{}, + User: "user", + }, + }, + msg: "disallowed on this kind of request", + }, + } + + for _, c := range errorCases { + errs := ValidateLocalSubjectAccessReview(c.obj) + if len(errs) == 0 { + t.Errorf("%s: expected failure for %q", c.name, c.msg) + } else if !strings.Contains(errs[0].Error(), c.msg) { + t.Errorf("%s: unexpected error: %q, expected: %q", c.name, errs[0], c.msg) + } + } +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_generated_expansion.go index 461a4698bcd..8c70e83f8b5 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_generated_expansion.go @@ -31,3 +31,8 @@ func (c *FakeSelfSubjectAccessReviews) Create(sar *authorizationapi.SelfSubjectA obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("selfsubjectaccessreviews"), sar), &authorizationapi.SelfSubjectAccessReview{}) return obj.(*authorizationapi.SelfSubjectAccessReview), err } + +func (c *FakeLocalSubjectAccessReviews) Create(sar *authorizationapi.LocalSubjectAccessReview) (result *authorizationapi.LocalSubjectAccessReview, err error) { + obj, err := c.Fake.Invokes(core.NewCreateAction(authorizationapi.SchemeGroupVersion.WithResource("localsubjectaccessreviews"), c.ns, sar), &authorizationapi.SubjectAccessReview{}) + return obj.(*authorizationapi.LocalSubjectAccessReview), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview_expansion.go new file mode 100644 index 00000000000..afe092a5860 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview_expansion.go @@ -0,0 +1,36 @@ +/* +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 unversioned + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" +) + +type LocalSubjectAccessReviewExpansion interface { + Create(sar *authorizationapi.LocalSubjectAccessReview) (result *authorizationapi.LocalSubjectAccessReview, err error) +} + +func (c *localSubjectAccessReviews) Create(sar *authorizationapi.LocalSubjectAccessReview) (result *authorizationapi.LocalSubjectAccessReview, err error) { + result = &authorizationapi.LocalSubjectAccessReview{} + err = c.client.Post(). + Namespace(c.ns). + Resource("localsubjectaccessreviews"). + Body(sar). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview_expansion.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview_expansion.go new file mode 100644 index 00000000000..d43b31c8ed2 --- /dev/null +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview_expansion.go @@ -0,0 +1,28 @@ +/* +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 fake + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" + + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeLocalSubjectAccessReviews) Create(sar *authorizationapi.LocalSubjectAccessReview) (result *authorizationapi.LocalSubjectAccessReview, err error) { + obj, err := c.Fake.Invokes(core.NewCreateAction(authorizationapi.SchemeGroupVersion.WithResource("localsubjectaccessreviews"), c.ns, sar), &authorizationapi.SubjectAccessReview{}) + return obj.(*authorizationapi.LocalSubjectAccessReview), err +} diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview_expansion.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview_expansion.go new file mode 100644 index 00000000000..3a3c64c1b82 --- /dev/null +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview_expansion.go @@ -0,0 +1,36 @@ +/* +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 v1beta1 + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" +) + +type LocalSubjectAccessReviewExpansion interface { + Create(sar *authorizationapi.LocalSubjectAccessReview) (result *authorizationapi.LocalSubjectAccessReview, err error) +} + +func (c *localSubjectAccessReviews) Create(sar *authorizationapi.LocalSubjectAccessReview) (result *authorizationapi.LocalSubjectAccessReview, err error) { + result = &authorizationapi.LocalSubjectAccessReview{} + err = c.client.Post(). + Namespace(c.ns). + Resource("localsubjectaccessreviews"). + Body(sar). + Do(). + Into(result) + return +} diff --git a/pkg/controller/garbagecollector/garbagecollector.go b/pkg/controller/garbagecollector/garbagecollector.go index d4a17f042f4..e35857eb8e9 100644 --- a/pkg/controller/garbagecollector/garbagecollector.go +++ b/pkg/controller/garbagecollector/garbagecollector.go @@ -525,13 +525,14 @@ func (gc *GarbageCollector) monitorFor(resource unversioned.GroupVersionResource } var ignoredResources = map[unversioned.GroupVersionResource]struct{}{ - unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicationcontrollers"}: {}, - unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "bindings"}: {}, - unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "componentstatuses"}: {}, - unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "events"}: {}, - unversioned.GroupVersionResource{Group: "authentication.k8s.io", Version: "v1beta1", Resource: "tokenreviews"}: {}, - unversioned.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "subjectaccessreviews"}: {}, - unversioned.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "selfsubjectaccessreviews"}: {}, + unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicationcontrollers"}: {}, + unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "bindings"}: {}, + unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "componentstatuses"}: {}, + unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "events"}: {}, + unversioned.GroupVersionResource{Group: "authentication.k8s.io", Version: "v1beta1", Resource: "tokenreviews"}: {}, + unversioned.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "subjectaccessreviews"}: {}, + unversioned.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "selfsubjectaccessreviews"}: {}, + unversioned.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "localsubjectaccessreviews"}: {}, } func NewGarbageCollector(metaOnlyClientPool dynamic.ClientPool, clientPool dynamic.ClientPool, resources []unversioned.GroupVersionResource) (*GarbageCollector, error) { diff --git a/pkg/master/storage_authorization.go b/pkg/master/storage_authorization.go index fb09eb075ba..ed0d7124db2 100644 --- a/pkg/master/storage_authorization.go +++ b/pkg/master/storage_authorization.go @@ -22,6 +22,7 @@ import ( authorizationv1beta1 "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" "k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/genericapiserver" + "k8s.io/kubernetes/pkg/registry/authorization/localsubjectaccessreview" "k8s.io/kubernetes/pkg/registry/authorization/selfsubjectaccessreview" "k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview" ) @@ -57,6 +58,9 @@ func (p AuthorizationRESTStorageProvider) v1beta1Storage(apiResourceConfigSource if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectaccessreviews")) { storage["selfsubjectaccessreviews"] = selfsubjectaccessreview.NewREST(p.Authorizer) } + if apiResourceConfigSource.ResourceEnabled(version.WithResource("localsubjectaccessreviews")) { + storage["localsubjectaccessreviews"] = localsubjectaccessreview.NewREST(p.Authorizer) + } return storage } diff --git a/pkg/registry/authorization/localsubjectaccessreview/rest.go b/pkg/registry/authorization/localsubjectaccessreview/rest.go new file mode 100644 index 00000000000..0695594299c --- /dev/null +++ b/pkg/registry/authorization/localsubjectaccessreview/rest.go @@ -0,0 +1,71 @@ +/* +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 localsubjectaccessreview + +import ( + "fmt" + + kapi "k8s.io/kubernetes/pkg/api" + kapierrors "k8s.io/kubernetes/pkg/api/errors" + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" + authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation" + "k8s.io/kubernetes/pkg/auth/authorizer" + authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + authorizer authorizer.Authorizer +} + +func NewREST(authorizer authorizer.Authorizer) *REST { + return &REST{authorizer} +} + +func (r *REST) New() runtime.Object { + return &authorizationapi.LocalSubjectAccessReview{} +} + +func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { + localSubjectAccessReview, ok := obj.(*authorizationapi.LocalSubjectAccessReview) + if !ok { + return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a LocaLocalSubjectAccessReview: %#v", obj)) + } + if errs := authorizationvalidation.ValidateLocalSubjectAccessReview(localSubjectAccessReview); len(errs) > 0 { + return nil, kapierrors.NewInvalid(authorizationapi.Kind(localSubjectAccessReview.Kind), "", errs) + } + namespace := kapi.NamespaceValue(ctx) + if len(namespace) == 0 { + return nil, kapierrors.NewBadRequest(fmt.Sprintf("namespace is required on this type: %v", namespace)) + } + if namespace != localSubjectAccessReview.Namespace { + return nil, kapierrors.NewBadRequest(fmt.Sprintf("spec.resourceAttributes.namespace must match namespace: %v", namespace)) + } + + authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(localSubjectAccessReview.Spec) + allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) + + localSubjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ + Allowed: allowed, + Reason: reason, + } + if evaluationErr != nil { + localSubjectAccessReview.Status.EvaluationError = evaluationErr.Error() + } + + return localSubjectAccessReview, nil +} diff --git a/pkg/registry/authorization/subjectaccessreview/rest.go b/pkg/registry/authorization/subjectaccessreview/rest.go index 1c293e5335c..f617853810f 100644 --- a/pkg/registry/authorization/subjectaccessreview/rest.go +++ b/pkg/registry/authorization/subjectaccessreview/rest.go @@ -24,7 +24,6 @@ import ( authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation" "k8s.io/kubernetes/pkg/auth/authorizer" - "k8s.io/kubernetes/pkg/auth/user" authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util" "k8s.io/kubernetes/pkg/runtime" ) @@ -50,19 +49,7 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err return nil, kapierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs) } - userToCheck := &user.DefaultInfo{ - Name: subjectAccessReview.Spec.User, - Groups: subjectAccessReview.Spec.Groups, - Extra: convertToUserInfoExtra(subjectAccessReview.Spec.Extra), - } - - var authorizationAttributes authorizer.AttributesRecord - if subjectAccessReview.Spec.ResourceAttributes != nil { - authorizationAttributes = authorizationutil.ResourceAttributesFrom(userToCheck, *subjectAccessReview.Spec.ResourceAttributes) - } else { - authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *subjectAccessReview.Spec.NonResourceAttributes) - } - + authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec) allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ @@ -75,15 +62,3 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err return subjectAccessReview, nil } - -func convertToUserInfoExtra(extra map[string]authorizationapi.ExtraValue) map[string][]string { - if extra == nil { - return nil - } - ret := map[string][]string{} - for k, v := range extra { - ret[k] = []string(v) - } - - return ret -} diff --git a/pkg/registry/authorization/util/helpers.go b/pkg/registry/authorization/util/helpers.go index 45b54a4ab49..710bb0bae34 100644 --- a/pkg/registry/authorization/util/helpers.go +++ b/pkg/registry/authorization/util/helpers.go @@ -42,3 +42,33 @@ func NonResourceAttributesFrom(user user.Info, in authorizationapi.NonResourceAt Path: in.Path, } } + +func convertToUserInfoExtra(extra map[string]authorizationapi.ExtraValue) map[string][]string { + if extra == nil { + return nil + } + ret := map[string][]string{} + for k, v := range extra { + ret[k] = []string(v) + } + + return ret +} + +// AuthorizationAttributesFrom takes a spec and returns the proper authz attributes to check it. +func AuthorizationAttributesFrom(spec authorizationapi.SubjectAccessReviewSpec) authorizer.AttributesRecord { + userToCheck := &user.DefaultInfo{ + Name: spec.User, + Groups: spec.Groups, + Extra: convertToUserInfoExtra(spec.Extra), + } + + var authorizationAttributes authorizer.AttributesRecord + if spec.ResourceAttributes != nil { + authorizationAttributes = ResourceAttributesFrom(userToCheck, *spec.ResourceAttributes) + } else { + authorizationAttributes = NonResourceAttributesFrom(userToCheck, *spec.NonResourceAttributes) + } + + return authorizationAttributes +} diff --git a/test/integration/auth/accessreview_test.go b/test/integration/auth/accessreview_test.go index 52a516a4cb9..a67bcfc9afd 100644 --- a/test/integration/auth/accessreview_test.go +++ b/test/integration/auth/accessreview_test.go @@ -245,3 +245,130 @@ func TestSelfSubjectAccessReview(t *testing.T) { } } } + +func TestLocalSubjectAccessReview(t *testing.T) { + var m *master.Master + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + m.Handler.ServeHTTP(w, req) + })) + defer s.Close() + + masterConfig := framework.NewIntegrationTestMasterConfig() + masterConfig.Authenticator = authenticator.RequestFunc(alwaysAlice) + masterConfig.Authorizer = sarAuthorizer{} + masterConfig.AdmissionControl = admit.NewAlwaysAdmit() + m, err := master.New(masterConfig) + if err != nil { + t.Fatalf("error in bringing up the master: %v", err) + } + + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + + tests := []struct { + name string + namespace string + sar *authorizationapi.LocalSubjectAccessReview + expectedError string + expectedStatus authorizationapi.SubjectAccessReviewStatus + }{ + { + name: "simple allow", + namespace: "foo", + sar: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: api.ObjectMeta{Namespace: "foo"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + Namespace: "foo", + }, + User: "alice", + }, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: true, + Reason: "you're not dave", + }, + }, + { + name: "simple deny", + namespace: "foo", + sar: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: api.ObjectMeta{Namespace: "foo"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + Namespace: "foo", + }, + User: "dave", + }, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: false, + Reason: "no", + EvaluationError: "I'm sorry, Dave", + }, + }, + { + name: "conflicting namespace", + namespace: "foo", + sar: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: api.ObjectMeta{Namespace: "foo"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + Namespace: "bar", + }, + User: "dave", + }, + }, + expectedError: "must match metadata.namespace", + }, + { + name: "missing namespace", + namespace: "foo", + sar: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: api.ObjectMeta{Namespace: "foo"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + }, + User: "dave", + }, + }, + expectedError: "must match metadata.namespace", + }, + } + + for _, test := range tests { + response, err := clientset.Authorization().LocalSubjectAccessReviews(test.namespace).Create(test.sar) + switch { + case err == nil && len(test.expectedError) == 0: + + case err != nil && strings.Contains(err.Error(), test.expectedError): + continue + + case err != nil && len(test.expectedError) != 0: + t.Errorf("%s: unexpected error: %v", test.name, err) + continue + default: + t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err) + continue + } + if response.Status != test.expectedStatus { + t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status) + continue + } + } +}