From 8fac64b43fb7cc289bf5228d7e96b008d6865300 Mon Sep 17 00:00:00 2001 From: deads2k Date: Fri, 9 Sep 2016 14:48:44 -0400 Subject: [PATCH 1/2] add localSAR --- pkg/apis/authorization/types.go | 3 + pkg/apis/authorization/v1beta1/types.go | 3 + .../authorization/validation/validation.go | 15 ++- .../validation/validation_test.go | 66 +++++++++ .../fake/fake_generated_expansion.go | 5 + .../localsubjectaccessreview_expansion.go | 36 +++++ ...fake_localsubjectaccessreview_expansion.go | 28 ++++ .../localsubjectaccessreview_expansion.go | 36 +++++ .../garbagecollector/garbagecollector.go | 15 ++- pkg/master/storage_authorization.go | 4 + .../localsubjectaccessreview/rest.go | 71 ++++++++++ .../authorization/subjectaccessreview/rest.go | 27 +--- pkg/registry/authorization/util/helpers.go | 30 +++++ test/integration/auth/accessreview_test.go | 127 ++++++++++++++++++ 14 files changed, 431 insertions(+), 35 deletions(-) create mode 100644 pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview_expansion.go create mode 100644 pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview_expansion.go create mode 100644 pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview_expansion.go create mode 100644 pkg/registry/authorization/localsubjectaccessreview/rest.go 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 + } + } +} From e5dbfdacc07e9dd7ad76498e574cc8be443edc4d Mon Sep 17 00:00:00 2001 From: deads2k Date: Fri, 9 Sep 2016 14:57:11 -0400 Subject: [PATCH 2/2] generated client --- .../authorization.k8s.io_v1beta1.json | 155 +++++++++++++----- .../unversioned/authorization_client.go | 5 + .../fake/fake_authorization_client.go | 4 + .../fake/fake_localsubjectaccessreview.go | 23 +++ .../unversioned/localsubjectaccessreview.go | 42 +++++ .../v1beta1/authorization_client.go | 5 + .../v1beta1/fake/fake_authorization_client.go | 4 + .../fake/fake_localsubjectaccessreview.go | 23 +++ .../v1beta1/localsubjectaccessreview.go | 42 +++++ 9 files changed, 266 insertions(+), 37 deletions(-) create mode 100644 pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_localsubjectaccessreview.go create mode 100644 pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview.go create mode 100644 pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview.go create mode 100644 pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview.go diff --git a/api/swagger-spec/authorization.k8s.io_v1beta1.json b/api/swagger-spec/authorization.k8s.io_v1beta1.json index eb539d24548..fa06c6185e3 100644 --- a/api/swagger-spec/authorization.k8s.io_v1beta1.json +++ b/api/swagger-spec/authorization.k8s.io_v1beta1.json @@ -8,6 +8,59 @@ "description": "" }, "apis": [ + { + "path": "/apis/authorization.k8s.io/v1beta1/namespaces/{namespace}/localsubjectaccessreviews", + "description": "API at /apis/authorization.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.LocalSubjectAccessReview", + "method": "POST", + "summary": "create a LocalSubjectAccessReview", + "nickname": "createNamespacedLocalSubjectAccessReview", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.LocalSubjectAccessReview", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.LocalSubjectAccessReview" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/apis/authorization.k8s.io/v1beta1/selfsubjectaccessreviews", "description": "API at /apis/authorization.k8s.io/v1beta1", @@ -123,9 +176,9 @@ } ], "models": { - "v1beta1.SelfSubjectAccessReview": { - "id": "v1beta1.SelfSubjectAccessReview", - "description": "SelfSubjectAccessReview checks whether or the current user can perform an action. Not filling in a spec.namespace means \"in all namespaces\". Self is a special case, because users should always be able to check whether they can perform an action", + "v1beta1.LocalSubjectAccessReview": { + "id": "v1beta1.LocalSubjectAccessReview", + "description": "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.", "required": [ "spec" ], @@ -142,8 +195,8 @@ "$ref": "v1.ObjectMeta" }, "spec": { - "$ref": "v1beta1.SelfSubjectAccessReviewSpec", - "description": "Spec holds information about the request being evaluated. user and groups must be empty" + "$ref": "v1beta1.SubjectAccessReviewSpec", + "description": "Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace you made the request against. If empty, it is defaulted." }, "status": { "$ref": "v1beta1.SubjectAccessReviewStatus", @@ -259,9 +312,9 @@ } } }, - "v1beta1.SelfSubjectAccessReviewSpec": { - "id": "v1beta1.SelfSubjectAccessReviewSpec", - "description": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + "v1beta1.SubjectAccessReviewSpec": { + "id": "v1beta1.SubjectAccessReviewSpec", + "description": "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", "properties": { "resourceAttributes": { "$ref": "v1beta1.ResourceAttributes", @@ -270,6 +323,21 @@ "nonResourceAttributes": { "$ref": "v1beta1.NonResourceAttributes", "description": "NonResourceAttributes describes information for a non-resource access request" + }, + "user": { + "type": "string", + "description": "User is the user you're testing for. If you specify \"User\" but not \"Group\", then is it interpreted as \"What if User were not a member of any groups" + }, + "group": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Groups is the groups you're testing for." + }, + "extra": { + "type": "object", + "description": "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here." } } }, @@ -342,6 +410,48 @@ } } }, + "v1beta1.SelfSubjectAccessReview": { + "id": "v1beta1.SelfSubjectAccessReview", + "description": "SelfSubjectAccessReview checks whether or the current user can perform an action. Not filling in a spec.namespace means \"in all namespaces\". Self is a special case, because users should always be able to check whether they can perform an action", + "required": [ + "spec" + ], + "properties": { + "kind": { + "type": "string", + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta" + }, + "spec": { + "$ref": "v1beta1.SelfSubjectAccessReviewSpec", + "description": "Spec holds information about the request being evaluated. user and groups must be empty" + }, + "status": { + "$ref": "v1beta1.SubjectAccessReviewStatus", + "description": "Status is filled in by the server and indicates whether the request is allowed or not" + } + } + }, + "v1beta1.SelfSubjectAccessReviewSpec": { + "id": "v1beta1.SelfSubjectAccessReviewSpec", + "description": "SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", + "properties": { + "resourceAttributes": { + "$ref": "v1beta1.ResourceAttributes", + "description": "ResourceAuthorizationAttributes describes information for a resource access request" + }, + "nonResourceAttributes": { + "$ref": "v1beta1.NonResourceAttributes", + "description": "NonResourceAttributes describes information for a non-resource access request" + } + } + }, "v1beta1.SubjectAccessReview": { "id": "v1beta1.SubjectAccessReview", "description": "SubjectAccessReview checks whether or not a user or group can perform an action.", @@ -370,35 +480,6 @@ } } }, - "v1beta1.SubjectAccessReviewSpec": { - "id": "v1beta1.SubjectAccessReviewSpec", - "description": "SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes and NonResourceAuthorizationAttributes must be set", - "properties": { - "resourceAttributes": { - "$ref": "v1beta1.ResourceAttributes", - "description": "ResourceAuthorizationAttributes describes information for a resource access request" - }, - "nonResourceAttributes": { - "$ref": "v1beta1.NonResourceAttributes", - "description": "NonResourceAttributes describes information for a non-resource access request" - }, - "user": { - "type": "string", - "description": "User is the user you're testing for. If you specify \"User\" but not \"Group\", then is it interpreted as \"What if User were not a member of any groups" - }, - "group": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Groups is the groups you're testing for." - }, - "extra": { - "type": "object", - "description": "Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer it needs a reflection here." - } - } - }, "unversioned.APIResourceList": { "id": "unversioned.APIResourceList", "description": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/authorization_client.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/authorization_client.go index cb7faf3ac01..b07517c8a54 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/authorization_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/authorization_client.go @@ -24,6 +24,7 @@ import ( type AuthorizationInterface interface { GetRESTClient() *restclient.RESTClient + LocalSubjectAccessReviewsGetter SelfSubjectAccessReviewsGetter SubjectAccessReviewsGetter } @@ -33,6 +34,10 @@ type AuthorizationClient struct { *restclient.RESTClient } +func (c *AuthorizationClient) LocalSubjectAccessReviews(namespace string) LocalSubjectAccessReviewInterface { + return newLocalSubjectAccessReviews(c, namespace) +} + func (c *AuthorizationClient) SelfSubjectAccessReviews() SelfSubjectAccessReviewInterface { return newSelfSubjectAccessReviews(c) } diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_authorization_client.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_authorization_client.go index b125d121683..62d962795a2 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_authorization_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_authorization_client.go @@ -26,6 +26,10 @@ type FakeAuthorization struct { *core.Fake } +func (c *FakeAuthorization) LocalSubjectAccessReviews(namespace string) unversioned.LocalSubjectAccessReviewInterface { + return &FakeLocalSubjectAccessReviews{c, namespace} +} + func (c *FakeAuthorization) SelfSubjectAccessReviews() unversioned.SelfSubjectAccessReviewInterface { return &FakeSelfSubjectAccessReviews{c} } diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_localsubjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_localsubjectaccessreview.go new file mode 100644 index 00000000000..50a1e3a8997 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_localsubjectaccessreview.go @@ -0,0 +1,23 @@ +/* +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 + +// FakeLocalSubjectAccessReviews implements LocalSubjectAccessReviewInterface +type FakeLocalSubjectAccessReviews struct { + Fake *FakeAuthorization + ns string +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview.go new file mode 100644 index 00000000000..4257a68a55e --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/localsubjectaccessreview.go @@ -0,0 +1,42 @@ +/* +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 + +// LocalSubjectAccessReviewsGetter has a method to return a LocalSubjectAccessReviewInterface. +// A group's client should implement this interface. +type LocalSubjectAccessReviewsGetter interface { + LocalSubjectAccessReviews(namespace string) LocalSubjectAccessReviewInterface +} + +// LocalSubjectAccessReviewInterface has methods to work with LocalSubjectAccessReview resources. +type LocalSubjectAccessReviewInterface interface { + LocalSubjectAccessReviewExpansion +} + +// localSubjectAccessReviews implements LocalSubjectAccessReviewInterface +type localSubjectAccessReviews struct { + client *AuthorizationClient + ns string +} + +// newLocalSubjectAccessReviews returns a LocalSubjectAccessReviews +func newLocalSubjectAccessReviews(c *AuthorizationClient, namespace string) *localSubjectAccessReviews { + return &localSubjectAccessReviews{ + client: c, + ns: namespace, + } +} diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/authorization_client.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/authorization_client.go index 43c4ddbbf13..7b55d185ae1 100644 --- a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/authorization_client.go +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/authorization_client.go @@ -25,6 +25,7 @@ import ( type AuthorizationInterface interface { GetRESTClient() *restclient.RESTClient + LocalSubjectAccessReviewsGetter SelfSubjectAccessReviewsGetter SubjectAccessReviewsGetter } @@ -34,6 +35,10 @@ type AuthorizationClient struct { *restclient.RESTClient } +func (c *AuthorizationClient) LocalSubjectAccessReviews(namespace string) LocalSubjectAccessReviewInterface { + return newLocalSubjectAccessReviews(c, namespace) +} + func (c *AuthorizationClient) SelfSubjectAccessReviews() SelfSubjectAccessReviewInterface { return newSelfSubjectAccessReviews(c) } diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_authorization_client.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_authorization_client.go index 719b41a083d..e9d34263731 100644 --- a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_authorization_client.go +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_authorization_client.go @@ -26,6 +26,10 @@ type FakeAuthorization struct { *core.Fake } +func (c *FakeAuthorization) LocalSubjectAccessReviews(namespace string) v1beta1.LocalSubjectAccessReviewInterface { + return &FakeLocalSubjectAccessReviews{c, namespace} +} + func (c *FakeAuthorization) SelfSubjectAccessReviews() v1beta1.SelfSubjectAccessReviewInterface { return &FakeSelfSubjectAccessReviews{c} } diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview.go new file mode 100644 index 00000000000..50a1e3a8997 --- /dev/null +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_localsubjectaccessreview.go @@ -0,0 +1,23 @@ +/* +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 + +// FakeLocalSubjectAccessReviews implements LocalSubjectAccessReviewInterface +type FakeLocalSubjectAccessReviews struct { + Fake *FakeAuthorization + ns string +} diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview.go new file mode 100644 index 00000000000..c605b8d04d2 --- /dev/null +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/localsubjectaccessreview.go @@ -0,0 +1,42 @@ +/* +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 + +// LocalSubjectAccessReviewsGetter has a method to return a LocalSubjectAccessReviewInterface. +// A group's client should implement this interface. +type LocalSubjectAccessReviewsGetter interface { + LocalSubjectAccessReviews(namespace string) LocalSubjectAccessReviewInterface +} + +// LocalSubjectAccessReviewInterface has methods to work with LocalSubjectAccessReview resources. +type LocalSubjectAccessReviewInterface interface { + LocalSubjectAccessReviewExpansion +} + +// localSubjectAccessReviews implements LocalSubjectAccessReviewInterface +type localSubjectAccessReviews struct { + client *AuthorizationClient + ns string +} + +// newLocalSubjectAccessReviews returns a LocalSubjectAccessReviews +func newLocalSubjectAccessReviews(c *AuthorizationClient, namespace string) *localSubjectAccessReviews { + return &localSubjectAccessReviews{ + client: c, + ns: namespace, + } +}