Add Authz check to validate policy and binding.

Co-authored-by: Jiahui Feng <jhf@google.com>
Co-authored-by: Jordan Liggitt <liggitt@google.com>
This commit is contained in:
Cici Huang 2022-11-07 21:29:56 +00:00
parent c8a089de46
commit d3f48136d0
15 changed files with 740 additions and 76 deletions

View File

@ -388,6 +388,14 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
return nil, err
}
clientset, err := kubernetes.NewForConfig(c.GenericConfig.LoopbackClientConfig)
if err != nil {
return nil, err
}
// TODO: update to a version that caches success but will recheck on failure, unlike memcache discovery
discoveryClientForAdmissionRegistration := clientset.Discovery()
// The order here is preserved in discovery.
// If resources with identical names exist in more than one of these groups (e.g. "deployments.apps"" and "deployments.extensions"),
// the order of this list determines which group an unqualified resource name (e.g. "deployments") should prefer.
@ -414,7 +422,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
// keep apps after extensions so legacy clients resolve the extensions versions of shared resource names.
// See https://github.com/kubernetes/kubernetes/issues/42392
appsrest.StorageProvider{},
admissionregistrationrest.RESTStorageProvider{},
admissionregistrationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, DiscoveryClient: discoveryClientForAdmissionRegistration},
eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL},
}
if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...); err != nil {

View File

@ -0,0 +1,72 @@
/*
Copyright 2022 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 resolver
import (
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
)
type ResourceResolver interface {
Resolve(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error)
}
type discoveryResourceResolver struct {
client discovery.DiscoveryInterface
}
func (d *discoveryResourceResolver) Resolve(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
gv := gvk.GroupVersion()
// TODO: refactor this into an efficient gvk --> gvr resolver that remembers hits and re-resolves group/version info on misses
resources, err := d.client.ServerResourcesForGroupVersion(gv.String())
if err != nil {
return schema.GroupVersionResource{}, err
}
for _, resource := range resources.APIResources {
if resource.Kind != gvk.Kind {
// ignore unrelated kinds
continue
}
if strings.Contains(resource.Name, "/") {
// ignore subresources
continue
}
if resource.Group != "" && resource.Group != gvk.Group {
// group didn't match
continue
}
if resource.Version != "" && resource.Version != gvk.Version {
// version didn't match
continue
}
return gv.WithResource(resource.Name), nil
}
return schema.GroupVersionResource{}, &meta.NoKindMatchError{GroupKind: gvk.GroupKind(), SearchedVersions: []string{gvk.Version}}
}
func NewDiscoveryResourceResolver(client discovery.DiscoveryInterface) (ResourceResolver, error) {
return &discoveryResourceResolver{client: client}, nil
}
type ResourceResolverFunc func(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error)
func (f ResourceResolverFunc) Resolve(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
return f(gvk)
}

View File

@ -19,19 +19,25 @@ package rest
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/client-go/discovery"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
mutatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
validatingadmissionpolicystorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicy/storage"
policybindingstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicybinding/storage"
validatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage"
)
type RESTStorageProvider struct{}
type RESTStorageProvider struct {
Authorizer authorizer.Authorizer
DiscoveryClient discovery.DiscoveryInterface
}
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(admissionregistration.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
@ -79,18 +85,27 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API
func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
storage := map[string]rest.Storage{}
// use a simple wrapper so that initialization order won't cause a nil getter
var policyGetter rest.Getter
r, err := resolver.NewDiscoveryResourceResolver(p.DiscoveryClient)
if err != nil {
return storage, err
}
// validatingadmissionpolicies
if resource := "validatingadmissionpolicies"; apiResourceConfigSource.ResourceEnabled(admissionregistrationv1alpha1.SchemeGroupVersion.WithResource(resource)) {
policyStorage, err := validatingadmissionpolicystorage.NewREST(restOptionsGetter)
policyStorage, err := validatingadmissionpolicystorage.NewREST(restOptionsGetter, p.Authorizer, r)
if err != nil {
return storage, err
}
policyGetter = policyStorage
storage[resource] = policyStorage
}
// validatingadmissionpolicybindings
if resource := "validatingadmissionpolicybindings"; apiResourceConfigSource.ResourceEnabled(admissionregistrationv1alpha1.SchemeGroupVersion.WithResource(resource)) {
policyBindingStorage, err := policybindingstorage.NewREST(restOptionsGetter)
policyBindingStorage, err := policybindingstorage.NewREST(restOptionsGetter, p.Authorizer, &policybindingstorage.DefaultPolicyGetter{Getter: policyGetter}, r)
if err != nil {
return storage, err
}

View File

@ -0,0 +1,105 @@
/*
Copyright 2022 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 validatingadmissionpolicy
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
)
func (v *validatingAdmissionPolicyStrategy) authorizeCreate(ctx context.Context, obj runtime.Object) error {
policy := obj.(*admissionregistration.ValidatingAdmissionPolicy)
if policy.Spec.ParamKind == nil {
// no paramRef in new object
return nil
}
return v.authorize(ctx, policy)
}
func (v *validatingAdmissionPolicyStrategy) authorizeUpdate(ctx context.Context, obj, old runtime.Object) error {
policy := obj.(*admissionregistration.ValidatingAdmissionPolicy)
if policy.Spec.ParamKind == nil {
// no paramRef in new object
return nil
}
oldPolicy := old.(*admissionregistration.ValidatingAdmissionPolicy)
if oldPolicy.Spec.ParamKind != nil && *oldPolicy.Spec.ParamKind == *policy.Spec.ParamKind {
// identical paramKind to old object
return nil
}
return v.authorize(ctx, policy)
}
func (v *validatingAdmissionPolicyStrategy) authorize(ctx context.Context, policy *admissionregistration.ValidatingAdmissionPolicy) error {
if v.authorizer == nil || policy.Spec.ParamKind == nil {
return nil
}
// for superuser, skip all checks
if rbacregistry.EscalationAllowed(ctx) {
return nil
}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return fmt.Errorf("cannot identify user to authorize read access to paramKind resources")
}
paramKind := policy.Spec.ParamKind
// default to requiring permissions on all group/version/resources
resource, apiGroup, apiVersion := "*", "*", "*"
if gv, err := schema.ParseGroupVersion(paramKind.APIVersion); err == nil {
// we only need to authorize the parsed group/version
apiGroup = gv.Group
apiVersion = gv.Version
if gvr, err := v.resourceResolver.Resolve(gv.WithKind(paramKind.Kind)); err == nil {
// we only need to authorize the resolved resource
resource = gvr.Resource
}
}
// require that the user can read (verb "get") the referred kind.
attrs := authorizer.AttributesRecord{
User: user,
Verb: "get",
ResourceRequest: true,
Name: "*",
Namespace: "*",
APIGroup: apiGroup,
APIVersion: apiVersion,
Resource: resource,
}
d, _, err := v.authorizer.Authorize(ctx, attrs)
if err != nil {
return err
}
if d != authorizer.DecisionAllow {
return fmt.Errorf(`user %v must have "get" permission on all objects of the referenced paramKind (kind=%s, apiVersion=%s)`, user, paramKind.Kind, paramKind.APIVersion)
}
return nil
}

View File

@ -0,0 +1,110 @@
/*
Copyright 2022 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 validatingadmissionpolicy
import (
"context"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
)
func TestAuthorization(t *testing.T) {
for _, tc := range []struct {
name string
userInfo user.Info
auth AuthFunc
resourceResolver resolver.ResourceResolverFunc
expectErr bool
}{
{
name: "superuser",
userInfo: &user.DefaultInfo{Groups: []string{user.SystemPrivilegedGroup}},
expectErr: false, // success despite always-denying authorizer
auth: func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return authorizer.DecisionDeny, "", nil
},
},
{
name: "authorized",
userInfo: &user.DefaultInfo{Groups: []string{user.AllAuthenticated}},
auth: func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
if a.GetResource() == "replicalimits" {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "", nil
},
resourceResolver: func(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
return schema.GroupVersionResource{
Group: "rules.example.com",
Version: "v1",
Resource: "replicalimits",
}, nil
},
expectErr: false,
},
{
name: "denied",
userInfo: &user.DefaultInfo{Groups: []string{user.AllAuthenticated}},
auth: func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
if a.GetResource() == "configmaps" {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "", nil
},
resourceResolver: func(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
return schema.GroupVersionResource{
Group: "rules.example.com",
Version: "v1",
Resource: "replicalimits",
}, nil
},
expectErr: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
strategy := NewStrategy(tc.auth, tc.resourceResolver)
t.Run("create", func(t *testing.T) {
ctx := request.WithUser(context.Background(), tc.userInfo)
errs := strategy.Validate(ctx, validValidatingAdmissionPolicy())
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
}
})
t.Run("update", func(t *testing.T) {
ctx := request.WithUser(context.Background(), tc.userInfo)
obj := validValidatingAdmissionPolicy()
objWithUpdatedParamKind := obj.DeepCopy()
objWithUpdatedParamKind.Spec.ParamKind.APIVersion += "1"
errs := strategy.ValidateUpdate(ctx, obj, objWithUpdatedParamKind)
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
}
})
})
}
}
type AuthFunc func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error)
func (f AuthFunc) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return f(ctx, a)
}

View File

@ -18,6 +18,7 @@ package storage
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
@ -25,6 +26,7 @@ import (
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
"k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicy"
)
@ -33,19 +35,23 @@ type REST struct {
*genericregistry.Store
}
var groupResource = admissionregistration.Resource("validatingadmissionpolicies")
// NewREST returns a RESTStorage object that will work against validatingAdmissionPolicy.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
func NewREST(optsGetter generic.RESTOptionsGetter, authorizer authorizer.Authorizer, resourceResolver resolver.ResourceResolver) (*REST, error) {
r := &REST{}
strategy := validatingadmissionpolicy.NewStrategy(authorizer, resourceResolver)
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicy{} },
NewListFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyList{} },
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*admissionregistration.ValidatingAdmissionPolicy).Name, nil
},
DefaultQualifiedResource: admissionregistration.Resource("validatingadmissionpolicies"),
DefaultQualifiedResource: groupResource,
CreateStrategy: validatingadmissionpolicy.Strategy,
UpdateStrategy: validatingadmissionpolicy.Strategy,
DeleteStrategy: validatingadmissionpolicy.Strategy,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -53,7 +59,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
if err := store.CompleteWithOptions(options); err != nil {
return nil, err
}
return &REST{store}, nil
r.Store = store
return r, nil
}
// Implement CategoriesProvider

View File

@ -23,10 +23,12 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
"k8s.io/kubernetes/pkg/registry/registrytest"
// Ensure that admissionregistration package is initialized.
@ -34,7 +36,7 @@ import (
)
func TestCreate(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -48,7 +50,7 @@ func TestCreate(t *testing.T) {
}
func TestUpdate(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -72,7 +74,7 @@ func TestUpdate(t *testing.T) {
}
func TestGet(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -80,7 +82,7 @@ func TestGet(t *testing.T) {
}
func TestList(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -88,7 +90,7 @@ func TestList(t *testing.T) {
}
func TestDelete(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -96,7 +98,7 @@ func TestDelete(t *testing.T) {
}
func TestWatch(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -198,14 +200,18 @@ func newValidatingAdmissionPolicy(name string) *admissionregistration.Validating
}
}
func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
func newInsecureStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
return newStorage(t, nil, nil)
}
func newStorage(t *testing.T, authorizer authorizer.Authorizer, resourceResolver resolver.ResourceResolver) (*REST, *etcd3testing.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorageForResource(t, admissionregistration.Resource("validatingadmissionpolicies"))
restOptions := generic.RESTOptions{
StorageConfig: etcdStorage,
Decorator: generic.UndecoratedStorage,
DeleteCollectionWorkers: 1,
ResourcePrefix: "validatingadmissionpolicies"}
storage, err := NewREST(restOptions)
storage, err := NewREST(restOptions, authorizer, resourceResolver)
if err != nil {
t.Fatalf("unexpected error from REST storage: %v", err)
}
@ -213,7 +219,7 @@ func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
}
func TestCategories(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
expected := []string{"api-extensions"}

View File

@ -18,37 +18,49 @@ package validatingadmissionpolicy
import (
"context"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
)
// validatingAdmissionPolicyStrategy implements verification logic for ValidatingAdmissionPolicy.
type validatingAdmissionPolicyStrategy struct {
runtime.ObjectTyper
names.NameGenerator
authorizer authorizer.Authorizer
resourceResolver resolver.ResourceResolver
}
// Strategy is the default logic that applies when creating and updating validatingAdmissionPolicy objects.
var Strategy = validatingAdmissionPolicyStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// NewStrategy is the default logic that applies when creating and updating validatingAdmissionPolicy objects.
func NewStrategy(authorizer authorizer.Authorizer, resourceResolver resolver.ResourceResolver) *validatingAdmissionPolicyStrategy {
return &validatingAdmissionPolicyStrategy{
ObjectTyper: legacyscheme.Scheme,
NameGenerator: names.SimpleNameGenerator,
authorizer: authorizer,
resourceResolver: resourceResolver,
}
}
// NamespaceScoped returns false because ValidatingAdmissionPolicy is cluster-scoped resource.
func (validatingAdmissionPolicyStrategy) NamespaceScoped() bool {
func (v *validatingAdmissionPolicyStrategy) NamespaceScoped() bool {
return false
}
// PrepareForCreate clears the status of an validatingAdmissionPolicy before creation.
func (validatingAdmissionPolicyStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
func (v *validatingAdmissionPolicyStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
ic := obj.(*admissionregistration.ValidatingAdmissionPolicy)
ic.Generation = 1
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (validatingAdmissionPolicyStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
func (v *validatingAdmissionPolicyStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newIC := obj.(*admissionregistration.ValidatingAdmissionPolicy)
oldIC := old.(*admissionregistration.ValidatingAdmissionPolicy)
@ -61,36 +73,50 @@ func (validatingAdmissionPolicyStrategy) PrepareForUpdate(ctx context.Context, o
}
// Validate validates a new validatingAdmissionPolicy.
func (validatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
return validation.ValidateValidatingAdmissionPolicy(obj.(*admissionregistration.ValidatingAdmissionPolicy))
func (v *validatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
errs := validation.ValidateValidatingAdmissionPolicy(obj.(*admissionregistration.ValidatingAdmissionPolicy))
if len(errs) == 0 {
// if the object is well-formed, also authorize the paramKind
if err := v.authorizeCreate(ctx, obj); err != nil {
errs = append(errs, field.Forbidden(field.NewPath("spec", "paramKind"), err.Error()))
}
}
return errs
}
// WarningsOnCreate returns warnings for the creation of the given object.
func (validatingAdmissionPolicyStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
func (v *validatingAdmissionPolicyStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
return nil
}
// Canonicalize normalizes the object after validation.
func (validatingAdmissionPolicyStrategy) Canonicalize(obj runtime.Object) {
func (v *validatingAdmissionPolicyStrategy) Canonicalize(obj runtime.Object) {
}
// AllowCreateOnUpdate is true for validatingAdmissionPolicy; this means you may create one with a PUT request.
func (validatingAdmissionPolicyStrategy) AllowCreateOnUpdate() bool {
func (v *validatingAdmissionPolicyStrategy) AllowCreateOnUpdate() bool {
return false
}
// ValidateUpdate is the default update validation for an end user.
func (validatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateValidatingAdmissionPolicyUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicy), old.(*admissionregistration.ValidatingAdmissionPolicy))
func (v *validatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
errs := validation.ValidateValidatingAdmissionPolicyUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicy), old.(*admissionregistration.ValidatingAdmissionPolicy))
if len(errs) == 0 {
// if the object is well-formed, also authorize the paramKind
if err := v.authorizeUpdate(ctx, obj, old); err != nil {
errs = append(errs, field.Forbidden(field.NewPath("spec", "paramKind"), err.Error()))
}
}
return errs
}
// WarningsOnUpdate returns warnings for the given update.
func (validatingAdmissionPolicyStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
func (v *validatingAdmissionPolicyStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil
}
// AllowUnconditionalUpdate is the default update policy for validatingAdmissionPolicy objects. Status update should
// only be allowed if version match.
func (validatingAdmissionPolicyStrategy) AllowUnconditionalUpdate() bool {
func (v *validatingAdmissionPolicyStrategy) AllowUnconditionalUpdate() bool {
return false
}

View File

@ -25,25 +25,26 @@ import (
)
func TestValidatingAdmissionPolicyStrategy(t *testing.T) {
strategy := NewStrategy(nil, nil)
ctx := genericapirequest.NewDefaultContext()
if Strategy.NamespaceScoped() {
if strategy.NamespaceScoped() {
t.Error("ValidatingAdmissionPolicy strategy must be cluster scoped")
}
if Strategy.AllowCreateOnUpdate() {
if strategy.AllowCreateOnUpdate() {
t.Errorf("ValidatingAdmissionPolicy should not allow create on update")
}
configuration := validValidatingAdmissionPolicy()
Strategy.PrepareForCreate(ctx, configuration)
errs := Strategy.Validate(ctx, configuration)
strategy.PrepareForCreate(ctx, configuration)
errs := strategy.Validate(ctx, configuration)
if len(errs) != 0 {
t.Errorf("Unexpected error validating %v", errs)
}
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{Name: ""},
}
Strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
errs = Strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
errs = strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
if len(errs) == 0 {
t.Errorf("Expected a validation error")
}

View File

@ -0,0 +1,110 @@
/*
Copyright 2022 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 validatingadmissionpolicybinding
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
)
func (v *validatingAdmissionPolicyBindingStrategy) authorizeCreate(ctx context.Context, obj runtime.Object) error {
binding := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
if binding.Spec.ParamRef == nil {
// no paramRef in new object
return nil
}
return v.authorize(ctx, binding)
}
func (v *validatingAdmissionPolicyBindingStrategy) authorizeUpdate(ctx context.Context, obj, old runtime.Object) error {
binding := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
if binding.Spec.ParamRef == nil {
// no paramRef in new object
return nil
}
oldBinding := old.(*admissionregistration.ValidatingAdmissionPolicyBinding)
if oldBinding.Spec.ParamRef != nil && *oldBinding.Spec.ParamRef == *binding.Spec.ParamRef && oldBinding.Spec.PolicyName == binding.Spec.PolicyName {
// identical paramRef and policy to old object
return nil
}
return v.authorize(ctx, binding)
}
func (v *validatingAdmissionPolicyBindingStrategy) authorize(ctx context.Context, binding *admissionregistration.ValidatingAdmissionPolicyBinding) error {
if v.authorizer == nil || v.resourceResolver == nil || binding.Spec.ParamRef == nil {
return nil
}
// for superuser, skip all checks
if rbacregistry.EscalationAllowed(ctx) {
return nil
}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return fmt.Errorf("cannot identify user to authorize read access to paramRef object")
}
// default to requiring permissions on all group/version/resources
resource, apiGroup, apiVersion := "*", "*", "*"
if policy, err := v.policyGetter.GetValidatingAdmissionPolicy(ctx, binding.Spec.PolicyName); err == nil && policy.Spec.ParamKind != nil {
paramKind := policy.Spec.ParamKind
if gv, err := schema.ParseGroupVersion(paramKind.APIVersion); err == nil {
// we only need to authorize the parsed group/version
apiGroup = gv.Group
apiVersion = gv.Version
if gvr, err := v.resourceResolver.Resolve(gv.WithKind(paramKind.Kind)); err == nil {
// we only need to authorize the resolved resource
resource = gvr.Resource
}
}
}
paramRef := binding.Spec.ParamRef
// require that the user can read (verb "get") the referred resource.
attrs := authorizer.AttributesRecord{
User: user,
Verb: "get",
ResourceRequest: true,
Name: paramRef.Name,
Namespace: paramRef.Namespace,
APIGroup: apiGroup,
APIVersion: apiVersion,
Resource: resource,
}
d, _, err := v.authorizer.Authorize(ctx, attrs)
if err != nil {
return err
}
if d != authorizer.DecisionAllow {
return fmt.Errorf(`user %v does not have "get" permission on the object referenced by paramRef`, user)
}
return nil
}

View File

@ -0,0 +1,135 @@
/*
Copyright 2022 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 validatingadmissionpolicybinding
import (
"context"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
)
func TestAuthorization(t *testing.T) {
for _, tc := range []struct {
name string
userInfo user.Info
auth AuthFunc
policyGetter PolicyGetterFunc
resourceResolver resolver.ResourceResolverFunc
expectErr bool
}{
{
name: "superuser",
userInfo: &user.DefaultInfo{Groups: []string{user.SystemPrivilegedGroup}},
expectErr: false, // success despite always-denying authorizer
auth: func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return authorizer.DecisionDeny, "", nil
},
},
{
name: "authorized",
userInfo: &user.DefaultInfo{Groups: []string{user.AllAuthenticated}},
auth: func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
if a.GetResource() == "configmaps" {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "", nil
},
policyGetter: func(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error) {
return &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{Name: "replicalimit-policy.example.com"},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
ParamKind: &admissionregistration.ParamKind{Kind: "ConfigMap", APIVersion: "v1"},
},
}, nil
},
resourceResolver: func(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
return schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "configmaps",
}, nil
},
expectErr: false,
},
{
name: "denied",
userInfo: &user.DefaultInfo{Groups: []string{user.AllAuthenticated}},
auth: func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
if a.GetResource() == "configmaps" {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "", nil
},
policyGetter: func(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error) {
return &admissionregistration.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{Name: "replicalimit-policy.example.com"},
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
ParamKind: &admissionregistration.ParamKind{Kind: "Params", APIVersion: "foo.example.com/v1"},
},
}, nil
},
resourceResolver: func(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
return schema.GroupVersionResource{
Group: "foo.example.com",
Version: "v1",
Resource: "params",
}, nil
},
expectErr: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
strategy := NewStrategy(tc.auth, tc.policyGetter, tc.resourceResolver)
t.Run("create", func(t *testing.T) {
ctx := request.WithUser(context.Background(), tc.userInfo)
errs := strategy.Validate(ctx, validPolicyBinding())
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
}
})
t.Run("update", func(t *testing.T) {
ctx := request.WithUser(context.Background(), tc.userInfo)
obj := validPolicyBinding()
objWithChangedParamRef := obj.DeepCopy()
objWithChangedParamRef.Spec.ParamRef.Name = "changed"
errs := strategy.ValidateUpdate(ctx, obj, objWithChangedParamRef)
if len(errs) > 0 != tc.expectErr {
t.Errorf("expected error: %v but got error: %v", tc.expectErr, errs)
}
})
})
}
}
type AuthFunc func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error)
func (f AuthFunc) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return f(ctx, a)
}
type PolicyGetterFunc func(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error)
func (f PolicyGetterFunc) GetValidatingAdmissionPolicy(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error) {
return f(ctx, name)
}

View File

@ -17,7 +17,11 @@ limitations under the License.
package storage
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
@ -25,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
"k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicybinding"
)
@ -33,19 +38,23 @@ type REST struct {
*genericregistry.Store
}
var groupResource = admissionregistration.Resource("validatingadmissionpolicybindings")
// NewREST returns a RESTStorage object that will work against policyBinding.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
func NewREST(optsGetter generic.RESTOptionsGetter, authorizer authorizer.Authorizer, policyGetter PolicyGetter, resourceResolver resolver.ResourceResolver) (*REST, error) {
r := &REST{}
strategy := validatingadmissionpolicybinding.NewStrategy(authorizer, policyGetter, resourceResolver)
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyBinding{} },
NewListFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyBindingList{} },
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*admissionregistration.ValidatingAdmissionPolicyBinding).Name, nil
},
DefaultQualifiedResource: admissionregistration.Resource("validatingadmissionpolicybindings"),
DefaultQualifiedResource: groupResource,
CreateStrategy: validatingadmissionpolicybinding.Strategy,
UpdateStrategy: validatingadmissionpolicybinding.Strategy,
DeleteStrategy: validatingadmissionpolicybinding.Strategy,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -53,7 +62,8 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
if err := store.CompleteWithOptions(options); err != nil {
return nil, err
}
return &REST{store}, nil
r.Store = store
return r, nil
}
// Implement CategoriesProvider
@ -63,3 +73,21 @@ var _ rest.CategoriesProvider = &REST{}
func (r *REST) Categories() []string {
return []string{"api-extensions"}
}
type PolicyGetter interface {
// GetValidatingAdmissionPolicy returns a GetValidatingAdmissionPolicy
// by its name. There is no namespace because it is cluster-scoped.
GetValidatingAdmissionPolicy(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error)
}
type DefaultPolicyGetter struct {
Getter rest.Getter
}
func (g *DefaultPolicyGetter) GetValidatingAdmissionPolicy(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error) {
p, err := g.Getter.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, err
}
return p.(*admissionregistration.ValidatingAdmissionPolicy), err
}

View File

@ -23,10 +23,12 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
"k8s.io/kubernetes/pkg/registry/registrytest"
// Ensure that admissionregistration package is initialized.
@ -34,7 +36,7 @@ import (
)
func TestCreate(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -48,7 +50,7 @@ func TestCreate(t *testing.T) {
}
func TestUpdate(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -72,7 +74,7 @@ func TestUpdate(t *testing.T) {
}
func TestGet(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -80,7 +82,7 @@ func TestGet(t *testing.T) {
}
func TestList(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -88,7 +90,7 @@ func TestList(t *testing.T) {
}
func TestDelete(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -96,7 +98,7 @@ func TestDelete(t *testing.T) {
}
func TestWatch(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
test := genericregistrytest.New(t, storage.Store).ClusterScope()
@ -169,14 +171,18 @@ func newPolicyBinding(name string) *admissionregistration.ValidatingAdmissionPol
}
}
func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
func newInsecureStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
return newStorage(t, nil, nil, nil)
}
func newStorage(t *testing.T, authorizer authorizer.Authorizer, policyGetter PolicyGetter, resourceResolver resolver.ResourceResolver) (*REST, *etcd3testing.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorageForResource(t, admissionregistration.Resource("validatingadmissionpolicybindings"))
restOptions := generic.RESTOptions{
StorageConfig: etcdStorage,
Decorator: generic.UndecoratedStorage,
DeleteCollectionWorkers: 1,
ResourcePrefix: "validatingadmissionpolicybindings"}
storage, err := NewREST(restOptions)
storage, err := NewREST(restOptions, authorizer, policyGetter, resourceResolver)
if err != nil {
t.Fatalf("unexpected error from REST storage: %v", err)
}
@ -184,7 +190,7 @@ func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
}
func TestCategories(t *testing.T) {
storage, server := newStorage(t)
storage, server := newInsecureStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
expected := []string{"api-extensions"}

View File

@ -18,37 +18,57 @@ package validatingadmissionpolicybinding
import (
"context"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
)
// ValidatingAdmissionPolicyBindingStrategy implements verification logic for ValidatingAdmissionPolicyBinding.
type ValidatingAdmissionPolicyBindingStrategy struct {
// validatingAdmissionPolicyBindingStrategy implements verification logic for ValidatingAdmissionPolicyBinding.
type validatingAdmissionPolicyBindingStrategy struct {
runtime.ObjectTyper
names.NameGenerator
authorizer authorizer.Authorizer
policyGetter PolicyGetter
resourceResolver resolver.ResourceResolver
}
// Strategy is the default logic that applies when creating and updating ValidatingAdmissionPolicyBinding objects.
var Strategy = ValidatingAdmissionPolicyBindingStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
type PolicyGetter interface {
// GetValidatingAdmissionPolicy returns a GetValidatingAdmissionPolicy
// by its name. There is no namespace because it is cluster-scoped.
GetValidatingAdmissionPolicy(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error)
}
// NewStrategy is the default logic that applies when creating and updating ValidatingAdmissionPolicyBinding objects.
func NewStrategy(authorizer authorizer.Authorizer, policyGetter PolicyGetter, resourceResolver resolver.ResourceResolver) *validatingAdmissionPolicyBindingStrategy {
return &validatingAdmissionPolicyBindingStrategy{
ObjectTyper: legacyscheme.Scheme,
NameGenerator: names.SimpleNameGenerator,
authorizer: authorizer,
policyGetter: policyGetter,
resourceResolver: resourceResolver,
}
}
// NamespaceScoped returns false because ValidatingAdmissionPolicyBinding is cluster-scoped resource.
func (ValidatingAdmissionPolicyBindingStrategy) NamespaceScoped() bool {
func (v *validatingAdmissionPolicyBindingStrategy) NamespaceScoped() bool {
return false
}
// PrepareForCreate clears the status of an ValidatingAdmissionPolicyBinding before creation.
func (ValidatingAdmissionPolicyBindingStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
func (v *validatingAdmissionPolicyBindingStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
ic := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
ic.Generation = 1
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (ValidatingAdmissionPolicyBindingStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
func (v *validatingAdmissionPolicyBindingStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newIC := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
oldIC := old.(*admissionregistration.ValidatingAdmissionPolicyBinding)
@ -61,36 +81,50 @@ func (ValidatingAdmissionPolicyBindingStrategy) PrepareForUpdate(ctx context.Con
}
// Validate validates a new ValidatingAdmissionPolicyBinding.
func (ValidatingAdmissionPolicyBindingStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
return validation.ValidateValidatingAdmissionPolicyBinding(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding))
func (v *validatingAdmissionPolicyBindingStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
errs := validation.ValidateValidatingAdmissionPolicyBinding(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding))
if len(errs) == 0 {
// if the object is well-formed, also authorize the paramRef
if err := v.authorizeCreate(ctx, obj); err != nil {
errs = append(errs, field.Forbidden(field.NewPath("spec", "paramRef"), err.Error()))
}
}
return errs
}
// WarningsOnCreate returns warnings for the creation of the given object.
func (ValidatingAdmissionPolicyBindingStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
func (v *validatingAdmissionPolicyBindingStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
return nil
}
// Canonicalize normalizes the object after validation.
func (ValidatingAdmissionPolicyBindingStrategy) Canonicalize(obj runtime.Object) {
func (v *validatingAdmissionPolicyBindingStrategy) Canonicalize(obj runtime.Object) {
}
// AllowCreateOnUpdate is true for ValidatingAdmissionPolicyBinding; this means you may create one with a PUT request.
func (ValidatingAdmissionPolicyBindingStrategy) AllowCreateOnUpdate() bool {
func (v *validatingAdmissionPolicyBindingStrategy) AllowCreateOnUpdate() bool {
return false
}
// ValidateUpdate is the default update validation for an end user.
func (ValidatingAdmissionPolicyBindingStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateValidatingAdmissionPolicyBindingUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding), old.(*admissionregistration.ValidatingAdmissionPolicyBinding))
func (v *validatingAdmissionPolicyBindingStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
errs := validation.ValidateValidatingAdmissionPolicyBindingUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding), old.(*admissionregistration.ValidatingAdmissionPolicyBinding))
if len(errs) == 0 {
// if the object is well-formed, also authorize the paramRef
if err := v.authorizeUpdate(ctx, obj, old); err != nil {
errs = append(errs, field.Forbidden(field.NewPath("spec", "paramRef"), err.Error()))
}
}
return errs
}
// WarningsOnUpdate returns warnings for the given update.
func (ValidatingAdmissionPolicyBindingStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
func (v *validatingAdmissionPolicyBindingStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil
}
// AllowUnconditionalUpdate is the default update policy for ValidatingAdmissionPolicyBinding objects. Status update should
// only be allowed if version match.
func (ValidatingAdmissionPolicyBindingStrategy) AllowUnconditionalUpdate() bool {
func (v *validatingAdmissionPolicyBindingStrategy) AllowUnconditionalUpdate() bool {
return false
}

View File

@ -25,25 +25,26 @@ import (
)
func TestPolicyBindingStrategy(t *testing.T) {
strategy := NewStrategy(nil, nil, nil)
ctx := genericapirequest.NewDefaultContext()
if Strategy.NamespaceScoped() {
if strategy.NamespaceScoped() {
t.Error("PolicyBinding strategy must be cluster scoped")
}
if Strategy.AllowCreateOnUpdate() {
if strategy.AllowCreateOnUpdate() {
t.Errorf("PolicyBinding should not allow create on update")
}
configuration := validPolicyBinding()
Strategy.PrepareForCreate(ctx, configuration)
errs := Strategy.Validate(ctx, configuration)
strategy.PrepareForCreate(ctx, configuration)
errs := strategy.Validate(ctx, configuration)
if len(errs) != 0 {
t.Errorf("Unexpected error validating %v", errs)
}
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{Name: ""},
}
Strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
errs = Strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
errs = strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
if len(errs) == 0 {
t.Errorf("Expected a validation error")
}