diff --git a/pkg/registry/authorization/subjectaccessreview/BUILD b/pkg/registry/authorization/subjectaccessreview/BUILD index 78ace523a3c..2762ac21212 100644 --- a/pkg/registry/authorization/subjectaccessreview/BUILD +++ b/pkg/registry/authorization/subjectaccessreview/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -34,3 +35,16 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["rest_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//pkg/apis/authorization:go_default_library", + "//vendor:k8s.io/apiserver/pkg/authentication/user", + "//vendor:k8s.io/apiserver/pkg/authorization/authorizer", + "//vendor:k8s.io/apiserver/pkg/endpoints/request", + ], +) diff --git a/pkg/registry/authorization/subjectaccessreview/rest_test.go b/pkg/registry/authorization/subjectaccessreview/rest_test.go new file mode 100644 index 00000000000..bb485b97b98 --- /dev/null +++ b/pkg/registry/authorization/subjectaccessreview/rest_test.go @@ -0,0 +1,198 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package subjectaccessreview + +import ( + "errors" + "strings" + "testing" + + "reflect" + + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" +) + +type fakeAuthorizer struct { + attrs authorizer.Attributes + + ok bool + reason string + err error +} + +func (f *fakeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, error) { + f.attrs = attrs + return f.ok, f.reason, f.err +} + +func TestCreate(t *testing.T) { + testcases := map[string]struct { + spec authorizationapi.SubjectAccessReviewSpec + ok bool + reason string + err error + + expectedErr string + expectedAttrs authorizer.Attributes + expectedStatus authorizationapi.SubjectAccessReviewStatus + }{ + "empty": { + expectedErr: "nonResourceAttributes or resourceAttributes", + }, + + "nonresource rejected": { + spec: authorizationapi.SubjectAccessReviewSpec{ + User: "bob", + NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"}, + }, + ok: false, + reason: "myreason", + err: errors.New("myerror"), + expectedAttrs: authorizer.AttributesRecord{ + User: &user.DefaultInfo{Name: "bob"}, + Verb: "get", + Path: "/mypath", + ResourceRequest: false, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: false, + Reason: "myreason", + EvaluationError: "myerror", + }, + }, + + "nonresource allowed": { + spec: authorizationapi.SubjectAccessReviewSpec{ + User: "bob", + NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"}, + }, + ok: true, + reason: "allowed", + err: nil, + expectedAttrs: authorizer.AttributesRecord{ + User: &user.DefaultInfo{Name: "bob"}, + Verb: "get", + Path: "/mypath", + ResourceRequest: false, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: true, + Reason: "allowed", + EvaluationError: "", + }, + }, + + "resource rejected": { + spec: authorizationapi.SubjectAccessReviewSpec{ + User: "bob", + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Namespace: "myns", + Verb: "create", + Group: "extensions", + Version: "v1beta1", + Resource: "deployments", + Subresource: "scale", + Name: "mydeployment", + }, + }, + ok: false, + reason: "myreason", + err: errors.New("myerror"), + expectedAttrs: authorizer.AttributesRecord{ + User: &user.DefaultInfo{Name: "bob"}, + Namespace: "myns", + Verb: "create", + APIGroup: "extensions", + APIVersion: "v1beta1", + Resource: "deployments", + Subresource: "scale", + Name: "mydeployment", + ResourceRequest: true, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: false, + Reason: "myreason", + EvaluationError: "myerror", + }, + }, + + "resource allowed": { + spec: authorizationapi.SubjectAccessReviewSpec{ + User: "bob", + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Namespace: "myns", + Verb: "create", + Group: "extensions", + Version: "v1beta1", + Resource: "deployments", + Subresource: "scale", + Name: "mydeployment", + }, + }, + ok: true, + reason: "allowed", + err: nil, + expectedAttrs: authorizer.AttributesRecord{ + User: &user.DefaultInfo{Name: "bob"}, + Namespace: "myns", + Verb: "create", + APIGroup: "extensions", + APIVersion: "v1beta1", + Resource: "deployments", + Subresource: "scale", + Name: "mydeployment", + ResourceRequest: true, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: true, + Reason: "allowed", + EvaluationError: "", + }, + }, + } + + for k, tc := range testcases { + auth := &fakeAuthorizer{ + ok: tc.ok, + reason: tc.reason, + err: tc.err, + } + rest := NewREST(auth) + + result, err := rest.Create(genericapirequest.NewContext(), &authorizationapi.SubjectAccessReview{Spec: tc.spec}) + if err != nil { + if tc.expectedErr != "" { + if !strings.Contains(err.Error(), tc.expectedErr) { + t.Errorf("%s: expected %s to contain %q", k, err, tc.expectedErr) + } + } else { + t.Errorf("%s: %v", k, err) + } + continue + } + if !reflect.DeepEqual(auth.attrs, tc.expectedAttrs) { + t.Errorf("%s: expected\n%#v\ngot\n%#v", k, tc.expectedAttrs, auth.attrs) + } + status := result.(*authorizationapi.SubjectAccessReview).Status + if !reflect.DeepEqual(status, tc.expectedStatus) { + t.Errorf("%s: expected\n%#v\ngot\n%#v", k, tc.expectedStatus, status) + } + } +} diff --git a/pkg/registry/authorization/util/BUILD b/pkg/registry/authorization/util/BUILD index de368d351ae..6792faa3e7b 100644 --- a/pkg/registry/authorization/util/BUILD +++ b/pkg/registry/authorization/util/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -30,3 +31,15 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["helpers_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//pkg/apis/authorization:go_default_library", + "//vendor:k8s.io/apimachinery/pkg/util/sets", + "//vendor:k8s.io/apiserver/pkg/authorization/authorizer", + ], +) diff --git a/pkg/registry/authorization/util/helpers.go b/pkg/registry/authorization/util/helpers.go index 38fac1b4368..18ad26ccda7 100644 --- a/pkg/registry/authorization/util/helpers.go +++ b/pkg/registry/authorization/util/helpers.go @@ -29,7 +29,10 @@ func ResourceAttributesFrom(user user.Info, in authorizationapi.ResourceAttribut Verb: in.Verb, Namespace: in.Namespace, APIGroup: in.Group, + APIVersion: in.Version, Resource: in.Resource, + Subresource: in.Subresource, + Name: in.Name, ResourceRequest: true, } } diff --git a/pkg/registry/authorization/util/helpers_test.go b/pkg/registry/authorization/util/helpers_test.go new file mode 100644 index 00000000000..0997d2ef126 --- /dev/null +++ b/pkg/registry/authorization/util/helpers_test.go @@ -0,0 +1,74 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/authorization/authorizer" + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" +) + +func TestResourceAttributesFrom(t *testing.T) { + knownResourceAttributesNames := sets.NewString( + // Fields we copy in ResourceAttributesFrom + "Verb", + "Namespace", + "Group", + "Version", + "Resource", + "Subresource", + "Name", + + // Fields we copy in NonResourceAttributesFrom + "Path", + "Verb", + ) + reflect.TypeOf(authorizationapi.ResourceAttributes{}).FieldByNameFunc(func(name string) bool { + if !knownResourceAttributesNames.Has(name) { + t.Errorf("authorizationapi.ResourceAttributes has a new field: %q. Add to ResourceAttributesFrom/NonResourceAttributesFrom as appropriate, then add to knownResourceAttributesNames", name) + } + return false + }) + + knownAttributesRecordFieldNames := sets.NewString( + // Fields we set in ResourceAttributesFrom + "User", + "Verb", + "Namespace", + "APIGroup", + "APIVersion", + "Resource", + "Subresource", + "Name", + "ResourceRequest", + + // Fields we set in NonResourceAttributesFrom + "User", + "ResourceRequest", + "Path", + "Verb", + ) + reflect.TypeOf(authorizer.AttributesRecord{}).FieldByNameFunc(func(name string) bool { + if !knownAttributesRecordFieldNames.Has(name) { + t.Errorf("authorizer.AttributesRecord has a new field: %q. Add to ResourceAttributesFrom/NonResourceAttributesFrom as appropriate, then add to knownAttributesRecordFieldNames", name) + } + return false + }) +}