add localSAR
This commit is contained in:
@@ -56,6 +56,9 @@ type SelfSubjectAccessReview struct {
|
|||||||
Status SubjectAccessReviewStatus
|
Status SubjectAccessReviewStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +genclient=true
|
||||||
|
// +noMethods=true
|
||||||
|
|
||||||
// LocalSubjectAccessReview checks whether or not a user or group can perform an action in a given namespace.
|
// 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
|
// Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions
|
||||||
// checking.
|
// checking.
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ type SelfSubjectAccessReview struct {
|
|||||||
Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
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.
|
// 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
|
// Having a namespace scoped resource makes it much easier to grant namespace scoped policy that includes permissions
|
||||||
// checking.
|
// checking.
|
||||||
|
|||||||
@@ -67,8 +67,19 @@ func ValidateSelfSubjectAccessReview(sar *authorizationapi.SelfSubjectAccessRevi
|
|||||||
|
|
||||||
func ValidateLocalSubjectAccessReview(sar *authorizationapi.LocalSubjectAccessReview) field.ErrorList {
|
func ValidateLocalSubjectAccessReview(sar *authorizationapi.LocalSubjectAccessReview) field.ErrorList {
|
||||||
allErrs := ValidateSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec"))
|
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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
|
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
|
||||||
"k8s.io/kubernetes/pkg/util/validation/field"
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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{})
|
obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("selfsubjectaccessreviews"), sar), &authorizationapi.SelfSubjectAccessReview{})
|
||||||
return obj.(*authorizationapi.SelfSubjectAccessReview), err
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -525,13 +525,14 @@ func (gc *GarbageCollector) monitorFor(resource unversioned.GroupVersionResource
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ignoredResources = map[unversioned.GroupVersionResource]struct{}{
|
var ignoredResources = map[unversioned.GroupVersionResource]struct{}{
|
||||||
unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicationcontrollers"}: {},
|
unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicationcontrollers"}: {},
|
||||||
unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "bindings"}: {},
|
unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "bindings"}: {},
|
||||||
unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "componentstatuses"}: {},
|
unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "componentstatuses"}: {},
|
||||||
unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "events"}: {},
|
unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "events"}: {},
|
||||||
unversioned.GroupVersionResource{Group: "authentication.k8s.io", Version: "v1beta1", Resource: "tokenreviews"}: {},
|
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: "subjectaccessreviews"}: {},
|
||||||
unversioned.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "selfsubjectaccessreviews"}: {},
|
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) {
|
func NewGarbageCollector(metaOnlyClientPool dynamic.ClientPool, clientPool dynamic.ClientPool, resources []unversioned.GroupVersionResource) (*GarbageCollector, error) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
authorizationv1beta1 "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
authorizationv1beta1 "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
||||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||||
"k8s.io/kubernetes/pkg/genericapiserver"
|
"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/selfsubjectaccessreview"
|
||||||
"k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview"
|
"k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview"
|
||||||
)
|
)
|
||||||
@@ -57,6 +58,9 @@ func (p AuthorizationRESTStorageProvider) v1beta1Storage(apiResourceConfigSource
|
|||||||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectaccessreviews")) {
|
if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectaccessreviews")) {
|
||||||
storage["selfsubjectaccessreviews"] = selfsubjectaccessreview.NewREST(p.Authorizer)
|
storage["selfsubjectaccessreviews"] = selfsubjectaccessreview.NewREST(p.Authorizer)
|
||||||
}
|
}
|
||||||
|
if apiResourceConfigSource.ResourceEnabled(version.WithResource("localsubjectaccessreviews")) {
|
||||||
|
storage["localsubjectaccessreviews"] = localsubjectaccessreview.NewREST(p.Authorizer)
|
||||||
|
}
|
||||||
|
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|||||||
71
pkg/registry/authorization/localsubjectaccessreview/rest.go
Normal file
71
pkg/registry/authorization/localsubjectaccessreview/rest.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
|
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
|
||||||
authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation"
|
authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation"
|
||||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||||
"k8s.io/kubernetes/pkg/auth/user"
|
|
||||||
authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util"
|
authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"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)
|
return nil, kapierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
userToCheck := &user.DefaultInfo{
|
authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes)
|
allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes)
|
||||||
|
|
||||||
subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{
|
subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{
|
||||||
@@ -75,15 +62,3 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err
|
|||||||
|
|
||||||
return subjectAccessReview, nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -42,3 +42,33 @@ func NonResourceAttributesFrom(user user.Info, in authorizationapi.NonResourceAt
|
|||||||
Path: in.Path,
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user