Merge pull request #39729 from sttts/sttts-split-server-side-rbac-validation

Automatic merge from submit-queue

Split out server-only code from api packages shared with the client

Fixes staging/copy.sh.
pull/6/head
Kubernetes Submit Queue 2017-01-11 13:31:43 -08:00 committed by GitHub
commit 330c922706
40 changed files with 302 additions and 202 deletions

View File

@ -29,11 +29,9 @@ go_library(
deps = [
"//pkg/api/resource:go_default_library",
"//pkg/fields:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/util/labels:go_default_library",
"//pkg/util/rand:go_default_library",
"//pkg/util/uuid:go_default_library",
"//vendor:github.com/davecgh/go-spew/spew",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
@ -88,8 +86,6 @@ go_test(
"//pkg/apis/batch/v2alpha1:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/util/uuid:go_default_library",
"//vendor:github.com/davecgh/go-spew/spew",
"//vendor:github.com/gogo/protobuf/proto",
"//vendor:github.com/golang/protobuf/proto",

View File

@ -22,23 +22,8 @@ import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
"k8s.io/kubernetes/pkg/util/uuid"
)
// FillObjectMetaSystemFields populates fields that are managed by the system on ObjectMeta.
func FillObjectMetaSystemFields(ctx genericapirequest.Context, meta *ObjectMeta) {
meta.CreationTimestamp = metav1.Now()
// allows admission controllers to assign a UID earlier in the request processing
// to support tracking resources pending creation.
uid, found := genericapirequest.UIDFrom(ctx)
if !found {
uid = uuid.NewUUID()
}
meta.UID = uid
meta.SelfLink = ""
}
// HasObjectMetaSystemFieldValues returns true if fields that are managed by the system on ObjectMeta have values.
func HasObjectMetaSystemFieldValues(meta *ObjectMeta) bool {
return !meta.CreationTimestamp.Time.IsZero() ||

View File

@ -28,45 +28,10 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
"k8s.io/kubernetes/pkg/util/uuid"
)
var _ meta.Object = &api.ObjectMeta{}
// TestFillObjectMetaSystemFields validates that system populated fields are set on an object
func TestFillObjectMetaSystemFields(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
resource := api.ObjectMeta{}
api.FillObjectMetaSystemFields(ctx, &resource)
if resource.CreationTimestamp.Time.IsZero() {
t.Errorf("resource.CreationTimestamp is zero")
} else if len(resource.UID) == 0 {
t.Errorf("resource.UID missing")
}
// verify we can inject a UID
uid := uuid.NewUUID()
ctx = genericapirequest.WithUID(ctx, uid)
resource = api.ObjectMeta{}
api.FillObjectMetaSystemFields(ctx, &resource)
if resource.UID != uid {
t.Errorf("resource.UID expected: %v, actual: %v", uid, resource.UID)
}
}
// TestHasObjectMetaSystemFieldValues validates that true is returned if and only if all fields are populated
func TestHasObjectMetaSystemFieldValues(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
resource := api.ObjectMeta{}
if api.HasObjectMetaSystemFieldValues(&resource) {
t.Errorf("the resource does not have all fields yet populated, but incorrectly reports it does")
}
api.FillObjectMetaSystemFields(ctx, &resource)
if !api.HasObjectMetaSystemFieldValues(&resource) {
t.Errorf("the resource does have all fields populated, but incorrectly reports it does not")
}
}
func getObjectMetaAndOwnerReferences() (objectMeta api.ObjectMeta, metaOwnerReferences []metav1.OwnerReference) {
fuzz.New().NilChance(.5).NumElements(1, 5).Fuzz(&objectMeta)
references := objectMeta.OwnerReferences

View File

@ -21,7 +21,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api/resource"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
)
// Returns string version of ResourceName.
@ -228,13 +227,3 @@ func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, li
}
return
}
// ValidNamespace returns false if the namespace on the context differs from the resource. If the resource has no namespace, it is set to the value in the context.
// TODO(sttts): move into pkg/genericapiserver/api
func ValidNamespace(ctx genericapirequest.Context, resource *ObjectMeta) bool {
ns, ok := genericapirequest.NamespaceFrom(ctx)
if len(resource.Namespace) == 0 {
resource.Namespace = ns
}
return ns == resource.Namespace && ok
}

View File

@ -5,6 +5,7 @@ licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
@ -14,6 +15,7 @@ go_library(
"delete.go",
"doc.go",
"export.go",
"meta.go",
"rest.go",
"types.go",
"update.go",
@ -25,6 +27,7 @@ go_library(
"//pkg/api/validation/genericvalidation:go_default_library",
"//pkg/api/validation/path:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/util/uuid:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
@ -49,3 +52,15 @@ filegroup(
],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["meta_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/util/uuid:go_default_library",
],
)

View File

@ -23,7 +23,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
path "k8s.io/kubernetes/pkg/api/validation/path"
"k8s.io/kubernetes/pkg/api/validation/path"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
)
@ -61,7 +61,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx genericapirequest.Context, ob
}
if strategy.NamespaceScoped() {
if !api.ValidNamespace(ctx, objectMeta) {
if !ValidNamespace(ctx, objectMeta) {
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
}
} else {
@ -70,7 +70,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx genericapirequest.Context, ob
objectMeta.DeletionTimestamp = nil
objectMeta.DeletionGracePeriodSeconds = nil
strategy.PrepareForCreate(ctx, obj)
api.FillObjectMetaSystemFields(ctx, objectMeta)
FillObjectMetaSystemFields(ctx, objectMeta)
api.GenerateName(strategy, objectMeta)
// ClusterName is ignored and should not be saved

47
pkg/api/rest/meta.go Normal file
View File

@ -0,0 +1,47 @@
/*
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 rest
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
"k8s.io/kubernetes/pkg/util/uuid"
)
// FillObjectMetaSystemFields populates fields that are managed by the system on ObjectMeta.
func FillObjectMetaSystemFields(ctx genericapirequest.Context, meta *api.ObjectMeta) {
meta.CreationTimestamp = metav1.Now()
// allows admission controllers to assign a UID earlier in the request processing
// to support tracking resources pending creation.
uid, found := genericapirequest.UIDFrom(ctx)
if !found {
uid = uuid.NewUUID()
}
meta.UID = uid
meta.SelfLink = ""
}
// ValidNamespace returns false if the namespace on the context differs from the resource. If the resource has no namespace, it is set to the value in the context.
// TODO(sttts): move into pkg/genericapiserver/api
func ValidNamespace(ctx genericapirequest.Context, resource *api.ObjectMeta) bool {
ns, ok := genericapirequest.NamespaceFrom(ctx)
if len(resource.Namespace) == 0 {
resource.Namespace = ns
}
return ns == resource.Namespace && ok
}

85
pkg/api/rest/meta_test.go Normal file
View File

@ -0,0 +1,85 @@
/*
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 rest
import (
"testing"
"k8s.io/kubernetes/pkg/api"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
"k8s.io/kubernetes/pkg/util/uuid"
)
// TestFillObjectMetaSystemFields validates that system populated fields are set on an object
func TestFillObjectMetaSystemFields(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
resource := api.ObjectMeta{}
FillObjectMetaSystemFields(ctx, &resource)
if resource.CreationTimestamp.Time.IsZero() {
t.Errorf("resource.CreationTimestamp is zero")
} else if len(resource.UID) == 0 {
t.Errorf("resource.UID missing")
}
// verify we can inject a UID
uid := uuid.NewUUID()
ctx = genericapirequest.WithUID(ctx, uid)
resource = api.ObjectMeta{}
FillObjectMetaSystemFields(ctx, &resource)
if resource.UID != uid {
t.Errorf("resource.UID expected: %v, actual: %v", uid, resource.UID)
}
}
// TestHasObjectMetaSystemFieldValues validates that true is returned if and only if all fields are populated
func TestHasObjectMetaSystemFieldValues(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
resource := api.ObjectMeta{}
if api.HasObjectMetaSystemFieldValues(&resource) {
t.Errorf("the resource does not have all fields yet populated, but incorrectly reports it does")
}
FillObjectMetaSystemFields(ctx, &resource)
if !api.HasObjectMetaSystemFieldValues(&resource) {
t.Errorf("the resource does have all fields populated, but incorrectly reports it does not")
}
}
// TestValidNamespace validates that namespace rules are enforced on a resource prior to create or update
func TestValidNamespace(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
namespace, _ := genericapirequest.NamespaceFrom(ctx)
resource := api.ReplicationController{}
if !ValidNamespace(ctx, &resource.ObjectMeta) {
t.Fatalf("expected success")
}
if namespace != resource.Namespace {
t.Fatalf("expected resource to have the default namespace assigned during validation")
}
resource = api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: "other"}}
if ValidNamespace(ctx, &resource.ObjectMeta) {
t.Fatalf("Expected error that resource and context errors do not match because resource has different namespace")
}
ctx = genericapirequest.NewContext()
if ValidNamespace(ctx, &resource.ObjectMeta) {
t.Fatalf("Expected error that resource and context errors do not match since context has no namespace")
}
ctx = genericapirequest.NewContext()
ns := genericapirequest.NamespaceValue(ctx)
if ns != "" {
t.Fatalf("Expected the empty string")
}
}

View File

@ -81,7 +81,7 @@ func BeforeUpdate(strategy RESTUpdateStrategy, ctx genericapirequest.Context, ob
return kerr
}
if strategy.NamespaceScoped() {
if !api.ValidNamespace(ctx, objectMeta) {
if !ValidNamespace(ctx, objectMeta) {
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
}
} else {

View File

@ -22,7 +22,6 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/conversion",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)

View File

@ -19,10 +19,13 @@ package v0
import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
api "k8s.io/kubernetes/pkg/apis/abac"
)
// allAuthenticated matches k8s.io/apiserver/pkg/authentication/user.AllAuthenticated,
// but we don't want an client library (which must include types), depending on a server library
const allAuthenticated = "system:authenticated"
func addConversionFuncs(scheme *runtime.Scheme) error {
return scheme.AddConversionFuncs(
func(in *Policy, out *api.Policy, s conversion.Scope) error {
@ -35,11 +38,11 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
// In v0, unspecified user and group matches all authenticated subjects
if len(in.User) == 0 && len(in.Group) == 0 {
out.Spec.Group = user.AllAuthenticated
out.Spec.Group = allAuthenticated
}
// In v0, user or group of * matches all authenticated subjects
if in.User == "*" || in.Group == "*" {
out.Spec.Group = user.AllAuthenticated
out.Spec.Group = allAuthenticated
out.Spec.User = ""
}

View File

@ -26,7 +26,6 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/conversion",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)

View File

@ -19,10 +19,13 @@ package v1beta1
import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
api "k8s.io/kubernetes/pkg/apis/abac"
)
// allAuthenticated matches k8s.io/apiserver/pkg/authentication/user.AllAuthenticated,
// but we don't want an client library (which must include types), depending on a server library
const allAuthenticated = "system:authenticated"
func addConversionFuncs(scheme *runtime.Scheme) error {
return scheme.AddConversionFuncs(
func(in *Policy, out *api.Policy, s conversion.Scope) error {
@ -33,7 +36,7 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
// In v1beta1, * user or group maps to all authenticated subjects
if in.Spec.User == "*" || in.Spec.Group == "*" {
out.Spec.Group = user.AllAuthenticated
out.Spec.Group = allAuthenticated
out.Spec.User = ""
}

View File

@ -10,41 +10,25 @@ load(
go_library(
name = "go_default_library",
srcs = [
"policy_comparator.go",
"rulevalidation.go",
"validation.go",
],
srcs = ["validation.go"],
tags = ["automanaged"],
deps = [
"//pkg/api/errors:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/api/validation/path:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/serviceaccount:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)
go_test(
name = "go_default_test",
srcs = [
"policy_comparator_test.go",
"rulevalidation_test.go",
"validation_test.go",
],
srcs = ["validation_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)

View File

@ -20,7 +20,7 @@ import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/rbac"
)
@ -293,31 +293,6 @@ func TestValidateRoleBindingUpdate(t *testing.T) {
}
}
func TestNonResourceURLCovers(t *testing.T) {
tests := []struct {
owner string
requested string
want bool
}{
{"*", "/api", true},
{"/api", "/api", true},
{"/apis", "/api", false},
{"/api/v1", "/api", false},
{"/api/v1", "/api/v1", true},
{"/api/*", "/api/v1", true},
{"/api/*", "/api", false},
{"/api/*/*", "/api/v1", false},
{"/*/v1/*", "/api/v1", false},
}
for _, tc := range tests {
got := nonResourceURLCovers(tc.owner, tc.requested)
if got != tc.want {
t.Errorf("nonResourceURLCovers(%q, %q): want=(%t), got=(%t)", tc.owner, tc.requested, tc.want, got)
}
}
}
type ValidateRoleTest struct {
role rbac.Role
wantErr bool

View File

@ -43,33 +43,6 @@ func TestNamespaceContext(t *testing.T) {
}
}
// TestValidNamespace validates that namespace rules are enforced on a resource prior to create or update
func TestValidNamespace(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
namespace, _ := genericapirequest.NamespaceFrom(ctx)
resource := api.ReplicationController{}
if !api.ValidNamespace(ctx, &resource.ObjectMeta) {
t.Fatalf("expected success")
}
if namespace != resource.Namespace {
t.Fatalf("expected resource to have the default namespace assigned during validation")
}
resource = api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: "other"}}
if api.ValidNamespace(ctx, &resource.ObjectMeta) {
t.Fatalf("Expected error that resource and context errors do not match because resource has different namespace")
}
ctx = genericapirequest.NewContext()
if api.ValidNamespace(ctx, &resource.ObjectMeta) {
t.Fatalf("Expected error that resource and context errors do not match since context has no namespace")
}
ctx = genericapirequest.NewContext()
ns := genericapirequest.NamespaceValue(ctx)
if ns != "" {
t.Fatalf("Expected the empty string")
}
}
//TestUserContext validates that a userinfo can be get/set on a context object
func TestUserContext(t *testing.T) {
ctx := genericapirequest.NewContext()

View File

@ -381,7 +381,7 @@ func (rs *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.
}
service := obj.(*api.Service)
if !api.ValidNamespace(ctx, &service.ObjectMeta) {
if !rest.ValidNamespace(ctx, &service.ObjectMeta) {
return nil, false, errors.NewConflict(api.Resource("services"), service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
}

View File

@ -36,6 +36,7 @@ filegroup(
"//pkg/registry/rbac/rest:all-srcs",
"//pkg/registry/rbac/role:all-srcs",
"//pkg/registry/rbac/rolebinding:all-srcs",
"//pkg/registry/rbac/validation:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -15,9 +15,9 @@ go_library(
"//pkg/api/errors:go_default_library",
"//pkg/api/rest:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/runtime",
],
)

View File

@ -22,9 +22,9 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("clusterroles")
@ -32,10 +32,10 @@ var groupResource = rbac.Resource("clusterroles")
type Storage struct {
rest.StandardStorage
ruleResolver validation.AuthorizationRuleResolver
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver) *Storage {
func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, ruleResolver}
}
@ -46,7 +46,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run
clusterRole := obj.(*rbac.ClusterRole)
rules := clusterRole.Rules
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, clusterRole.Name, err)
}
return s.StandardStorage.Create(ctx, obj)
@ -61,7 +61,7 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up
clusterRole := obj.(*rbac.ClusterRole)
rules := clusterRole.Rules
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, clusterRole.Name, err)
}
return obj, nil

View File

@ -15,9 +15,9 @@ go_library(
"//pkg/api/errors:go_default_library",
"//pkg/api/rest:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
],

View File

@ -23,9 +23,9 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("clusterrolebindings")
@ -35,10 +35,10 @@ type Storage struct {
authorizer authorizer.Authorizer
ruleResolver validation.AuthorizationRuleResolver
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver validation.AuthorizationRuleResolver) *Storage {
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, authorizer, ruleResolver}
}
@ -56,7 +56,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run
if err != nil {
return nil, err
}
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, clusterRoleBinding.Name, err)
}
return s.StandardStorage.Create(ctx, obj)
@ -80,7 +80,7 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up
if err != nil {
return nil, err
}
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, clusterRoleBinding.Name, err)
}
return obj, nil

View File

@ -16,7 +16,6 @@ go_library(
"//pkg/api/rest:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/v1alpha1:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
"//pkg/genericapiserver:go_default_library",
"//pkg/registry/generic:go_default_library",
@ -32,6 +31,7 @@ go_library(
"//pkg/registry/rbac/rolebinding:go_default_library",
"//pkg/registry/rbac/rolebinding/etcd:go_default_library",
"//pkg/registry/rbac/rolebinding/policybased:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",

View File

@ -30,7 +30,6 @@ import (
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacapiv1alpha1 "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1"
rbacvalidation "k8s.io/kubernetes/pkg/apis/rbac/validation"
rbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
"k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/registry/generic"
@ -46,6 +45,7 @@ import (
"k8s.io/kubernetes/pkg/registry/rbac/rolebinding"
rolebindingetcd "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/etcd"
rolebindingpolicybased "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/policybased"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
@ -71,7 +71,7 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource genericapis
once := new(sync.Once)
var (
authorizationRuleResolver rbacvalidation.AuthorizationRuleResolver
authorizationRuleResolver rbacregistryvalidation.AuthorizationRuleResolver
rolesStorage rest.StandardStorage
roleBindingsStorage rest.StandardStorage
clusterRolesStorage rest.StandardStorage
@ -85,7 +85,7 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource genericapis
clusterRolesStorage = clusterroleetcd.NewREST(restOptionsGetter)
clusterRoleBindingsStorage = clusterrolebindingetcd.NewREST(restOptionsGetter)
authorizationRuleResolver = rbacvalidation.NewDefaultRuleResolver(
authorizationRuleResolver = rbacregistryvalidation.NewDefaultRuleResolver(
role.AuthorizerAdapter{Registry: role.NewRegistry(rolesStorage)},
rolebinding.AuthorizerAdapter{Registry: rolebinding.NewRegistry(roleBindingsStorage)},
clusterrole.AuthorizerAdapter{Registry: clusterrole.NewRegistry(clusterRolesStorage)},

View File

@ -15,9 +15,9 @@ go_library(
"//pkg/api/errors:go_default_library",
"//pkg/api/rest:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/runtime",
],
)

View File

@ -22,9 +22,9 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("roles")
@ -32,10 +32,10 @@ var groupResource = rbac.Resource("roles")
type Storage struct {
rest.StandardStorage
ruleResolver validation.AuthorizationRuleResolver
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver) *Storage {
func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, ruleResolver}
}
@ -46,7 +46,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run
role := obj.(*rbac.Role)
rules := role.Rules
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, role.Name, err)
}
return s.StandardStorage.Create(ctx, obj)
@ -61,7 +61,7 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up
role := obj.(*rbac.Role)
rules := role.Rules
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, role.Name, err)
}
return obj, nil

View File

@ -15,9 +15,9 @@ go_library(
"//pkg/api/errors:go_default_library",
"//pkg/api/rest:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
],

View File

@ -23,9 +23,9 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
var groupResource = rbac.Resource("rolebindings")
@ -35,10 +35,10 @@ type Storage struct {
authorizer authorizer.Authorizer
ruleResolver validation.AuthorizationRuleResolver
ruleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver validation.AuthorizationRuleResolver) *Storage {
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver rbacregistryvalidation.AuthorizationRuleResolver) *Storage {
return &Storage{s, authorizer, ruleResolver}
}
@ -56,7 +56,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run
if err != nil {
return nil, err
}
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, roleBinding.Name, err)
}
return s.StandardStorage.Create(ctx, obj)
@ -80,7 +80,7 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up
if err != nil {
return nil, err
}
if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil {
return nil, errors.NewForbidden(groupResource, roleBinding.Name, err)
}
return obj, nil

View File

@ -0,0 +1,56 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"policy_comparator_test.go",
"rule_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)
go_library(
name = "go_default_library",
srcs = [
"policy_comparator.go",
"rule.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api/errors:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/genericapiserver/api/request:go_default_library",
"//pkg/serviceaccount:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -400,3 +400,28 @@ func rulesMatch(expectedRules, actualRules []rbac.PolicyRule) bool {
return true
}
func TestNonResourceURLCovers(t *testing.T) {
tests := []struct {
owner string
requested string
want bool
}{
{"*", "/api", true},
{"/api", "/api", true},
{"/apis", "/api", false},
{"/api/v1", "/api", false},
{"/api/v1", "/api/v1", true},
{"/api/*", "/api/v1", true},
{"/api/*", "/api", false},
{"/api/*/*", "/api/v1", false},
{"/*/v1/*", "/api/v1", false},
}
for _, tc := range tests {
got := nonResourceURLCovers(tc.owner, tc.requested)
if got != tc.want {
t.Errorf("nonResourceURLCovers(%q, %q): want=(%t), got=(%t)", tc.owner, tc.requested, tc.want, got)
}
}
}

View File

@ -17,7 +17,7 @@ go_library(
tags = ["automanaged"],
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
@ -36,7 +36,7 @@ go_test(
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
],

View File

@ -37,7 +37,7 @@ go_test(
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/install:go_default_library",
"//pkg/apis/rbac/v1alpha1:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//vendor:github.com/ghodss/yaml",
"//vendor:k8s.io/apimachinery/pkg/api/meta",

View File

@ -32,10 +32,10 @@ import (
"k8s.io/kubernetes/pkg/api"
_ "k8s.io/kubernetes/pkg/api/install"
"k8s.io/kubernetes/pkg/api/v1"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
rbacv1alpha1 "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1"
rbacvalidation "k8s.io/kubernetes/pkg/apis/rbac/validation"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
@ -67,13 +67,13 @@ func getSemanticRoles(roles []rbac.ClusterRole) semanticRoles {
func TestCovers(t *testing.T) {
semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
if covers, miss := rbacvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.view.Rules); !covers {
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
}
@ -91,17 +91,17 @@ func TestAdminEditRelationship(t *testing.T) {
// confirm that the edit role doesn't already have extra powers
for _, rule := range additionalAdminPowers {
if covers, _ := rbacvalidation.Covers(semanticRoles.edit.Rules, []rbac.PolicyRule{rule}); covers {
if covers, _ := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, []rbac.PolicyRule{rule}); covers {
t.Errorf("edit has extra powers: %#v", rule)
}
}
semanticRoles.edit.Rules = append(semanticRoles.edit.Rules, additionalAdminPowers...)
// at this point, we should have a two way covers relationship
if covers, miss := rbacvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("admin has lost rules for: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.admin.Rules); !covers {
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.admin.Rules); !covers {
t.Errorf("edit is missing rules for: %#v\nIf these should only be admin powers, add them to the list. Otherwise, add them to the edit role.", miss)
}
}
@ -136,17 +136,17 @@ func TestEditViewRelationship(t *testing.T) {
// confirm that the view role doesn't already have extra powers
for _, rule := range viewEscalatingNamespaceResources {
if covers, _ := rbacvalidation.Covers(semanticRoles.view.Rules, []rbac.PolicyRule{rule}); covers {
if covers, _ := rbacregistryvalidation.Covers(semanticRoles.view.Rules, []rbac.PolicyRule{rule}); covers {
t.Errorf("view has extra powers: %#v", rule)
}
}
semanticRoles.view.Rules = append(semanticRoles.view.Rules, viewEscalatingNamespaceResources...)
// at this point, we should have a two way covers relationship
if covers, miss := rbacvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("edit has lost rules for: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.view.Rules, semanticRoles.edit.Rules); !covers {
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.view.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("view is missing rules for: %#v\nIf these are escalating powers, add them to the list. Otherwise, add them to the view role.", miss)
}
}

View File

@ -24,7 +24,7 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
type RequestToRuleMapper interface {
@ -55,9 +55,9 @@ func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (boo
return false, reason, nil
}
func New(roles validation.RoleGetter, roleBindings validation.RoleBindingLister, clusterRoles validation.ClusterRoleGetter, clusterRoleBindings validation.ClusterRoleBindingLister) *RBACAuthorizer {
func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer {
authorizer := &RBACAuthorizer{
authorizationRuleResolver: validation.NewDefaultRuleResolver(
authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}

View File

@ -25,7 +25,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
func newRule(verbs, apiGroups, resources, nonResourceURLs string) rbac.PolicyRule {
@ -219,7 +219,7 @@ func TestAuthorizer(t *testing.T) {
},
}
for i, tt := range tests {
ruleResolver, _ := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
ruleResolver, _ := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := RBACAuthorizer{ruleResolver}
for _, attr := range tt.shouldPass {
if authorized, _, _ := a.Authorize(attr); !authorized {

View File

@ -22,7 +22,7 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
type RoleToRuleMapper interface {
@ -34,17 +34,17 @@ type RoleToRuleMapper interface {
type SubjectAccessEvaluator struct {
superUser string
roleBindingLister validation.RoleBindingLister
clusterRoleBindingLister validation.ClusterRoleBindingLister
roleBindingLister rbacregistryvalidation.RoleBindingLister
clusterRoleBindingLister rbacregistryvalidation.ClusterRoleBindingLister
roleToRuleMapper RoleToRuleMapper
}
func NewSubjectAccessEvaluator(roles validation.RoleGetter, roleBindings validation.RoleBindingLister, clusterRoles validation.ClusterRoleGetter, clusterRoleBindings validation.ClusterRoleBindingLister, superUser string) *SubjectAccessEvaluator {
func NewSubjectAccessEvaluator(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister, superUser string) *SubjectAccessEvaluator {
subjectLocator := &SubjectAccessEvaluator{
superUser: superUser,
roleBindingLister: roleBindings,
clusterRoleBindingLister: clusterRoleBindings,
roleToRuleMapper: validation.NewDefaultRuleResolver(
roleToRuleMapper: rbacregistryvalidation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}

View File

@ -23,7 +23,7 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
func TestSubjectLocator(t *testing.T) {
@ -136,7 +136,7 @@ func TestSubjectLocator(t *testing.T) {
},
}
for _, tt := range tests {
ruleResolver, lister := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
ruleResolver, lister := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := SubjectAccessEvaluator{tt.superUser, lister, lister, ruleResolver}
for i, action := range tt.actionsToSubjects {
actualSubjects, err := a.AllowedSubjects(action.action)