diff --git a/pkg/apis/authorization/types.go b/pkg/apis/authorization/types.go index 33ad58c3539..37f271f8b4f 100644 --- a/pkg/apis/authorization/types.go +++ b/pkg/apis/authorization/types.go @@ -38,6 +38,10 @@ type SubjectAccessReview struct { Status SubjectAccessReviewStatus } +// +genclient=true +// +nonNamespaced=true +// +noMethods=true + // 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 diff --git a/pkg/apis/authorization/v1beta1/types.go b/pkg/apis/authorization/v1beta1/types.go index 558a1e5750b..2cdc69dc57e 100644 --- a/pkg/apis/authorization/v1beta1/types.go +++ b/pkg/apis/authorization/v1beta1/types.go @@ -39,6 +39,10 @@ type SubjectAccessReview struct { Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } +// +genclient=true +// +nonNamespaced=true +// +noMethods=true + // 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 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 54eed3652d4..461a4698bcd 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 @@ -26,3 +26,8 @@ func (c *FakeSubjectAccessReviews) Create(sar *authorizationapi.SubjectAccessRev obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("subjectaccessreviews"), sar), &authorizationapi.SubjectAccessReview{}) return obj.(*authorizationapi.SubjectAccessReview), err } + +func (c *FakeSelfSubjectAccessReviews) Create(sar *authorizationapi.SelfSubjectAccessReview) (result *authorizationapi.SelfSubjectAccessReview, err error) { + obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("selfsubjectaccessreviews"), sar), &authorizationapi.SelfSubjectAccessReview{}) + return obj.(*authorizationapi.SelfSubjectAccessReview), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/selfsubjectaccessreview_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/selfsubjectaccessreview_expansion.go new file mode 100644 index 00000000000..0cde6e83b41 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/selfsubjectaccessreview_expansion.go @@ -0,0 +1,35 @@ +/* +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 SelfSubjectAccessReviewExpansion interface { + Create(sar *authorizationapi.SelfSubjectAccessReview) (result *authorizationapi.SelfSubjectAccessReview, err error) +} + +func (c *selfSubjectAccessReviews) Create(sar *authorizationapi.SelfSubjectAccessReview) (result *authorizationapi.SelfSubjectAccessReview, err error) { + result = &authorizationapi.SelfSubjectAccessReview{} + err = c.client.Post(). + Resource("selfsubjectaccessreviews"). + Body(sar). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/subjectaccessreview_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/subjectaccessreview_expansion.go index 16a170ece21..ce6e91eb449 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/subjectaccessreview_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/subjectaccessreview_expansion.go @@ -20,7 +20,6 @@ import ( authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" ) -// The PodExpansion interface allows manually adding extra methods to the PodInterface. type SubjectAccessReviewExpansion interface { Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReview, err error) } diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_subjectaccessreview_expansion.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_subjectaccessreview_expansion.go index 88df166ef58..2356220f986 100644 --- a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_subjectaccessreview_expansion.go +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_subjectaccessreview_expansion.go @@ -26,3 +26,8 @@ func (c *FakeSubjectAccessReviews) Create(sar *authorizationapi.SubjectAccessRev obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("subjectaccessreviews"), sar), &authorizationapi.SubjectAccessReview{}) return obj.(*authorizationapi.SubjectAccessReview), err } + +func (c *FakeSelfSubjectAccessReviews) Create(sar *authorizationapi.SelfSubjectAccessReview) (result *authorizationapi.SelfSubjectAccessReview, err error) { + obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("selfsubjectaccessreviews"), sar), &authorizationapi.SelfSubjectAccessReview{}) + return obj.(*authorizationapi.SelfSubjectAccessReview), err +} diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/selfsubjectaccessreview_expansion.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/selfsubjectaccessreview_expansion.go new file mode 100644 index 00000000000..a64678243ec --- /dev/null +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/selfsubjectaccessreview_expansion.go @@ -0,0 +1,35 @@ +/* +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 SelfSubjectAccessReviewExpansion interface { + Create(sar *authorizationapi.SelfSubjectAccessReview) (result *authorizationapi.SelfSubjectAccessReview, err error) +} + +func (c *selfSubjectAccessReviews) Create(sar *authorizationapi.SelfSubjectAccessReview) (result *authorizationapi.SelfSubjectAccessReview, err error) { + result = &authorizationapi.SelfSubjectAccessReview{} + err = c.client.Post(). + Resource("selfsubjectaccessreviews"). + Body(sar). + Do(). + Into(result) + return +} diff --git a/pkg/controller/garbagecollector/garbagecollector.go b/pkg/controller/garbagecollector/garbagecollector.go index dbe4f30d689..b269e09586c 100644 --- a/pkg/controller/garbagecollector/garbagecollector.go +++ b/pkg/controller/garbagecollector/garbagecollector.go @@ -525,12 +525,13 @@ 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: "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"}: {}, } 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 0ddb4f7a50c..fb09eb075ba 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/selfsubjectaccessreview" "k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview" ) @@ -53,6 +54,9 @@ func (p AuthorizationRESTStorageProvider) v1beta1Storage(apiResourceConfigSource if apiResourceConfigSource.ResourceEnabled(version.WithResource("subjectaccessreviews")) { storage["subjectaccessreviews"] = subjectaccessreview.NewREST(p.Authorizer) } + if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectaccessreviews")) { + storage["selfsubjectaccessreviews"] = selfsubjectaccessreview.NewREST(p.Authorizer) + } return storage } diff --git a/pkg/registry/authorization/selfsubjectaccessreview/rest.go b/pkg/registry/authorization/selfsubjectaccessreview/rest.go new file mode 100644 index 00000000000..bdb9bbbf070 --- /dev/null +++ b/pkg/registry/authorization/selfsubjectaccessreview/rest.go @@ -0,0 +1,74 @@ +/* +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 selfsubjectaccessreview + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + apierrors "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.SelfSubjectAccessReview{} +} + +func (r *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + selfSAR, ok := obj.(*authorizationapi.SelfSubjectAccessReview) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectAccessReview: %#v", obj)) + } + if errs := authorizationvalidation.ValidateSelfSubjectAccessReview(selfSAR); len(errs) > 0 { + return nil, apierrors.NewInvalid(authorizationapi.Kind(selfSAR.Kind), "", errs) + } + userToCheck, exists := api.UserFrom(ctx) + if !exists { + return nil, apierrors.NewBadRequest("no user present on request") + } + + var authorizationAttributes authorizer.AttributesRecord + if selfSAR.Spec.ResourceAttributes != nil { + authorizationAttributes = authorizationutil.ResourceAttributesFrom(userToCheck, *selfSAR.Spec.ResourceAttributes) + } else { + authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *selfSAR.Spec.NonResourceAttributes) + } + + allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) + + selfSAR.Status = authorizationapi.SubjectAccessReviewStatus{ + Allowed: allowed, + Reason: reason, + } + if evaluationErr != nil { + selfSAR.Status.EvaluationError = evaluationErr.Error() + } + + return selfSAR, nil +} diff --git a/test/integration/auth/accessreview_test.go b/test/integration/auth/accessreview_test.go index 6b76e0fc9d9..52a516a4cb9 100644 --- a/test/integration/auth/accessreview_test.go +++ b/test/integration/auth/accessreview_test.go @@ -57,7 +57,6 @@ func alwaysAlice(req *http.Request) (user.Info, bool, error) { } func TestSubjectAccessReview(t *testing.T) { - // Set up a master var m *master.Master s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { m.Handler.ServeHTTP(w, req) @@ -153,7 +152,96 @@ func TestSubjectAccessReview(t *testing.T) { t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status) continue } + } +} +func TestSelfSubjectAccessReview(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() + + username := "alice" + masterConfig := framework.NewIntegrationTestMasterConfig() + masterConfig.Authenticator = authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { + return &user.DefaultInfo{Name: username}, true, nil + }) + 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 + username string + sar *authorizationapi.SelfSubjectAccessReview + expectedError string + expectedStatus authorizationapi.SubjectAccessReviewStatus + }{ + { + name: "simple allow", + username: "alice", + sar: &authorizationapi.SelfSubjectAccessReview{ + Spec: authorizationapi.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + }, + }, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: true, + Reason: "you're not dave", + }, + }, + { + name: "simple deny", + username: "dave", + sar: &authorizationapi.SelfSubjectAccessReview{ + Spec: authorizationapi.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + }, + }, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: false, + Reason: "no", + EvaluationError: "I'm sorry, Dave", + }, + }, + } + + for _, test := range tests { + username = test.username + + response, err := clientset.Authorization().SelfSubjectAccessReviews().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 + } + } }