mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 14:37:00 +00:00
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:
parent
c8a089de46
commit
d3f48136d0
@ -388,6 +388,14 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
|
|||||||
return nil, err
|
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.
|
// 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"),
|
// 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.
|
// 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.
|
// keep apps after extensions so legacy clients resolve the extensions versions of shared resource names.
|
||||||
// See https://github.com/kubernetes/kubernetes/issues/42392
|
// See https://github.com/kubernetes/kubernetes/issues/42392
|
||||||
appsrest.StorageProvider{},
|
appsrest.StorageProvider{},
|
||||||
admissionregistrationrest.RESTStorageProvider{},
|
admissionregistrationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, DiscoveryClient: discoveryClientForAdmissionRegistration},
|
||||||
eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL},
|
eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL},
|
||||||
}
|
}
|
||||||
if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...); err != nil {
|
if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...); err != nil {
|
||||||
|
72
pkg/registry/admissionregistration/resolver/resolver.go
Normal file
72
pkg/registry/admissionregistration/resolver/resolver.go
Normal 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)
|
||||||
|
}
|
@ -19,19 +19,25 @@ package rest
|
|||||||
import (
|
import (
|
||||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||||
mutatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage"
|
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"
|
validatingadmissionpolicystorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicy/storage"
|
||||||
policybindingstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicybinding/storage"
|
policybindingstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicybinding/storage"
|
||||||
validatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration/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) {
|
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) {
|
||||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(admissionregistration.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
|
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) {
|
func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
|
||||||
storage := map[string]rest.Storage{}
|
storage := map[string]rest.Storage{}
|
||||||
|
|
||||||
// validatingadmissionpolicies
|
// use a simple wrapper so that initialization order won't cause a nil getter
|
||||||
if resource := "validatingadmissionpolicies"; apiResourceConfigSource.ResourceEnabled(admissionregistrationv1alpha1.SchemeGroupVersion.WithResource(resource)) {
|
var policyGetter rest.Getter
|
||||||
policyStorage, err := validatingadmissionpolicystorage.NewREST(restOptionsGetter)
|
|
||||||
|
r, err := resolver.NewDiscoveryResourceResolver(p.DiscoveryClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return storage, err
|
return storage, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validatingadmissionpolicies
|
||||||
|
if resource := "validatingadmissionpolicies"; apiResourceConfigSource.ResourceEnabled(admissionregistrationv1alpha1.SchemeGroupVersion.WithResource(resource)) {
|
||||||
|
policyStorage, err := validatingadmissionpolicystorage.NewREST(restOptionsGetter, p.Authorizer, r)
|
||||||
|
if err != nil {
|
||||||
|
return storage, err
|
||||||
|
}
|
||||||
|
policyGetter = policyStorage
|
||||||
storage[resource] = policyStorage
|
storage[resource] = policyStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatingadmissionpolicybindings
|
// validatingadmissionpolicybindings
|
||||||
if resource := "validatingadmissionpolicybindings"; apiResourceConfigSource.ResourceEnabled(admissionregistrationv1alpha1.SchemeGroupVersion.WithResource(resource)) {
|
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 {
|
if err != nil {
|
||||||
return storage, err
|
return storage, err
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -18,6 +18,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
@ -25,6 +26,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
|
||||||
"k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicy"
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,19 +35,23 @@ type REST struct {
|
|||||||
*genericregistry.Store
|
*genericregistry.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var groupResource = admissionregistration.Resource("validatingadmissionpolicies")
|
||||||
|
|
||||||
// NewREST returns a RESTStorage object that will work against validatingAdmissionPolicy.
|
// 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{
|
store := &genericregistry.Store{
|
||||||
NewFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicy{} },
|
NewFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicy{} },
|
||||||
NewListFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyList{} },
|
NewListFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyList{} },
|
||||||
ObjectNameFunc: func(obj runtime.Object) (string, error) {
|
ObjectNameFunc: func(obj runtime.Object) (string, error) {
|
||||||
return obj.(*admissionregistration.ValidatingAdmissionPolicy).Name, nil
|
return obj.(*admissionregistration.ValidatingAdmissionPolicy).Name, nil
|
||||||
},
|
},
|
||||||
DefaultQualifiedResource: admissionregistration.Resource("validatingadmissionpolicies"),
|
DefaultQualifiedResource: groupResource,
|
||||||
|
|
||||||
CreateStrategy: validatingadmissionpolicy.Strategy,
|
CreateStrategy: strategy,
|
||||||
UpdateStrategy: validatingadmissionpolicy.Strategy,
|
UpdateStrategy: strategy,
|
||||||
DeleteStrategy: validatingadmissionpolicy.Strategy,
|
DeleteStrategy: strategy,
|
||||||
|
|
||||||
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
|
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 {
|
if err := store.CompleteWithOptions(options); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &REST{store}, nil
|
r.Store = store
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement CategoriesProvider
|
// Implement CategoriesProvider
|
||||||
|
@ -23,10 +23,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||||
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
|
||||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||||
|
|
||||||
// Ensure that admissionregistration package is initialized.
|
// Ensure that admissionregistration package is initialized.
|
||||||
@ -34,7 +36,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -48,7 +50,7 @@ func TestCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -72,7 +74,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -80,7 +82,7 @@ func TestGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -88,7 +90,7 @@ func TestList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -96,7 +98,7 @@ func TestDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
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"))
|
etcdStorage, server := registrytest.NewEtcdStorageForResource(t, admissionregistration.Resource("validatingadmissionpolicies"))
|
||||||
restOptions := generic.RESTOptions{
|
restOptions := generic.RESTOptions{
|
||||||
StorageConfig: etcdStorage,
|
StorageConfig: etcdStorage,
|
||||||
Decorator: generic.UndecoratedStorage,
|
Decorator: generic.UndecoratedStorage,
|
||||||
DeleteCollectionWorkers: 1,
|
DeleteCollectionWorkers: 1,
|
||||||
ResourcePrefix: "validatingadmissionpolicies"}
|
ResourcePrefix: "validatingadmissionpolicies"}
|
||||||
storage, err := NewREST(restOptions)
|
storage, err := NewREST(restOptions, authorizer, resourceResolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error from REST storage: %v", err)
|
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) {
|
func TestCategories(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
expected := []string{"api-extensions"}
|
expected := []string{"api-extensions"}
|
||||||
|
@ -18,37 +18,49 @@ package validatingadmissionpolicy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validatingAdmissionPolicyStrategy implements verification logic for ValidatingAdmissionPolicy.
|
// validatingAdmissionPolicyStrategy implements verification logic for ValidatingAdmissionPolicy.
|
||||||
type validatingAdmissionPolicyStrategy struct {
|
type validatingAdmissionPolicyStrategy struct {
|
||||||
runtime.ObjectTyper
|
runtime.ObjectTyper
|
||||||
names.NameGenerator
|
names.NameGenerator
|
||||||
|
authorizer authorizer.Authorizer
|
||||||
|
resourceResolver resolver.ResourceResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy is the default logic that applies when creating and updating validatingAdmissionPolicy objects.
|
// NewStrategy is the default logic that applies when creating and updating validatingAdmissionPolicy objects.
|
||||||
var Strategy = validatingAdmissionPolicyStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
|
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.
|
// NamespaceScoped returns false because ValidatingAdmissionPolicy is cluster-scoped resource.
|
||||||
func (validatingAdmissionPolicyStrategy) NamespaceScoped() bool {
|
func (v *validatingAdmissionPolicyStrategy) NamespaceScoped() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForCreate clears the status of an validatingAdmissionPolicy before creation.
|
// 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 := obj.(*admissionregistration.ValidatingAdmissionPolicy)
|
||||||
ic.Generation = 1
|
ic.Generation = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
// 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)
|
newIC := obj.(*admissionregistration.ValidatingAdmissionPolicy)
|
||||||
oldIC := old.(*admissionregistration.ValidatingAdmissionPolicy)
|
oldIC := old.(*admissionregistration.ValidatingAdmissionPolicy)
|
||||||
|
|
||||||
@ -61,36 +73,50 @@ func (validatingAdmissionPolicyStrategy) PrepareForUpdate(ctx context.Context, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates a new validatingAdmissionPolicy.
|
// Validate validates a new validatingAdmissionPolicy.
|
||||||
func (validatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (v *validatingAdmissionPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
return validation.ValidateValidatingAdmissionPolicy(obj.(*admissionregistration.ValidatingAdmissionPolicy))
|
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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Canonicalize normalizes the object after validation.
|
// 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.
|
// 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUpdate is the default update validation for an end user.
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
func (validatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (v *validatingAdmissionPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
return validation.ValidateValidatingAdmissionPolicyUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicy), old.(*admissionregistration.ValidatingAdmissionPolicy))
|
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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowUnconditionalUpdate is the default update policy for validatingAdmissionPolicy objects. Status update should
|
// AllowUnconditionalUpdate is the default update policy for validatingAdmissionPolicy objects. Status update should
|
||||||
// only be allowed if version match.
|
// only be allowed if version match.
|
||||||
func (validatingAdmissionPolicyStrategy) AllowUnconditionalUpdate() bool {
|
func (v *validatingAdmissionPolicyStrategy) AllowUnconditionalUpdate() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -25,25 +25,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestValidatingAdmissionPolicyStrategy(t *testing.T) {
|
func TestValidatingAdmissionPolicyStrategy(t *testing.T) {
|
||||||
|
strategy := NewStrategy(nil, nil)
|
||||||
ctx := genericapirequest.NewDefaultContext()
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
if Strategy.NamespaceScoped() {
|
if strategy.NamespaceScoped() {
|
||||||
t.Error("ValidatingAdmissionPolicy strategy must be cluster scoped")
|
t.Error("ValidatingAdmissionPolicy strategy must be cluster scoped")
|
||||||
}
|
}
|
||||||
if Strategy.AllowCreateOnUpdate() {
|
if strategy.AllowCreateOnUpdate() {
|
||||||
t.Errorf("ValidatingAdmissionPolicy should not allow create on update")
|
t.Errorf("ValidatingAdmissionPolicy should not allow create on update")
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration := validValidatingAdmissionPolicy()
|
configuration := validValidatingAdmissionPolicy()
|
||||||
Strategy.PrepareForCreate(ctx, configuration)
|
strategy.PrepareForCreate(ctx, configuration)
|
||||||
errs := Strategy.Validate(ctx, configuration)
|
errs := strategy.Validate(ctx, configuration)
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
t.Errorf("Unexpected error validating %v", errs)
|
t.Errorf("Unexpected error validating %v", errs)
|
||||||
}
|
}
|
||||||
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicy{
|
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicy{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: ""},
|
ObjectMeta: metav1.ObjectMeta{Name: ""},
|
||||||
}
|
}
|
||||||
Strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
|
strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
|
||||||
errs = Strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
|
errs = strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("Expected a validation error")
|
t.Errorf("Expected a validation error")
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -17,7 +17,11 @@ limitations under the License.
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
@ -25,6 +29,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
|
||||||
"k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicybinding"
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/validatingadmissionpolicybinding"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,19 +38,23 @@ type REST struct {
|
|||||||
*genericregistry.Store
|
*genericregistry.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var groupResource = admissionregistration.Resource("validatingadmissionpolicybindings")
|
||||||
|
|
||||||
// NewREST returns a RESTStorage object that will work against policyBinding.
|
// 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{
|
store := &genericregistry.Store{
|
||||||
NewFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyBinding{} },
|
NewFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyBinding{} },
|
||||||
NewListFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyBindingList{} },
|
NewListFunc: func() runtime.Object { return &admissionregistration.ValidatingAdmissionPolicyBindingList{} },
|
||||||
ObjectNameFunc: func(obj runtime.Object) (string, error) {
|
ObjectNameFunc: func(obj runtime.Object) (string, error) {
|
||||||
return obj.(*admissionregistration.ValidatingAdmissionPolicyBinding).Name, nil
|
return obj.(*admissionregistration.ValidatingAdmissionPolicyBinding).Name, nil
|
||||||
},
|
},
|
||||||
DefaultQualifiedResource: admissionregistration.Resource("validatingadmissionpolicybindings"),
|
DefaultQualifiedResource: groupResource,
|
||||||
|
|
||||||
CreateStrategy: validatingadmissionpolicybinding.Strategy,
|
CreateStrategy: strategy,
|
||||||
UpdateStrategy: validatingadmissionpolicybinding.Strategy,
|
UpdateStrategy: strategy,
|
||||||
DeleteStrategy: validatingadmissionpolicybinding.Strategy,
|
DeleteStrategy: strategy,
|
||||||
|
|
||||||
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
|
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 {
|
if err := store.CompleteWithOptions(options); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &REST{store}, nil
|
r.Store = store
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement CategoriesProvider
|
// Implement CategoriesProvider
|
||||||
@ -63,3 +73,21 @@ var _ rest.CategoriesProvider = &REST{}
|
|||||||
func (r *REST) Categories() []string {
|
func (r *REST) Categories() []string {
|
||||||
return []string{"api-extensions"}
|
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
|
||||||
|
}
|
||||||
|
@ -23,10 +23,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||||
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
|
||||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||||
|
|
||||||
// Ensure that admissionregistration package is initialized.
|
// Ensure that admissionregistration package is initialized.
|
||||||
@ -34,7 +36,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -48,7 +50,7 @@ func TestCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -72,7 +74,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -80,7 +82,7 @@ func TestGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -88,7 +90,7 @@ func TestList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
@ -96,7 +98,7 @@ func TestDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
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"))
|
etcdStorage, server := registrytest.NewEtcdStorageForResource(t, admissionregistration.Resource("validatingadmissionpolicybindings"))
|
||||||
restOptions := generic.RESTOptions{
|
restOptions := generic.RESTOptions{
|
||||||
StorageConfig: etcdStorage,
|
StorageConfig: etcdStorage,
|
||||||
Decorator: generic.UndecoratedStorage,
|
Decorator: generic.UndecoratedStorage,
|
||||||
DeleteCollectionWorkers: 1,
|
DeleteCollectionWorkers: 1,
|
||||||
ResourcePrefix: "validatingadmissionpolicybindings"}
|
ResourcePrefix: "validatingadmissionpolicybindings"}
|
||||||
storage, err := NewREST(restOptions)
|
storage, err := NewREST(restOptions, authorizer, policyGetter, resourceResolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error from REST storage: %v", err)
|
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) {
|
func TestCategories(t *testing.T) {
|
||||||
storage, server := newStorage(t)
|
storage, server := newInsecureStorage(t)
|
||||||
defer server.Terminate(t)
|
defer server.Terminate(t)
|
||||||
defer storage.Store.DestroyFunc()
|
defer storage.Store.DestroyFunc()
|
||||||
expected := []string{"api-extensions"}
|
expected := []string{"api-extensions"}
|
||||||
|
@ -18,37 +18,57 @@ package validatingadmissionpolicybinding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/admissionregistration/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidatingAdmissionPolicyBindingStrategy implements verification logic for ValidatingAdmissionPolicyBinding.
|
// validatingAdmissionPolicyBindingStrategy implements verification logic for ValidatingAdmissionPolicyBinding.
|
||||||
type ValidatingAdmissionPolicyBindingStrategy struct {
|
type validatingAdmissionPolicyBindingStrategy struct {
|
||||||
runtime.ObjectTyper
|
runtime.ObjectTyper
|
||||||
names.NameGenerator
|
names.NameGenerator
|
||||||
|
authorizer authorizer.Authorizer
|
||||||
|
policyGetter PolicyGetter
|
||||||
|
resourceResolver resolver.ResourceResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy is the default logic that applies when creating and updating ValidatingAdmissionPolicyBinding objects.
|
type PolicyGetter interface {
|
||||||
var Strategy = ValidatingAdmissionPolicyBindingStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
|
// 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.
|
// NamespaceScoped returns false because ValidatingAdmissionPolicyBinding is cluster-scoped resource.
|
||||||
func (ValidatingAdmissionPolicyBindingStrategy) NamespaceScoped() bool {
|
func (v *validatingAdmissionPolicyBindingStrategy) NamespaceScoped() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForCreate clears the status of an ValidatingAdmissionPolicyBinding before creation.
|
// 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 := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
|
||||||
ic.Generation = 1
|
ic.Generation = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
// 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)
|
newIC := obj.(*admissionregistration.ValidatingAdmissionPolicyBinding)
|
||||||
oldIC := old.(*admissionregistration.ValidatingAdmissionPolicyBinding)
|
oldIC := old.(*admissionregistration.ValidatingAdmissionPolicyBinding)
|
||||||
|
|
||||||
@ -61,36 +81,50 @@ func (ValidatingAdmissionPolicyBindingStrategy) PrepareForUpdate(ctx context.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates a new ValidatingAdmissionPolicyBinding.
|
// Validate validates a new ValidatingAdmissionPolicyBinding.
|
||||||
func (ValidatingAdmissionPolicyBindingStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (v *validatingAdmissionPolicyBindingStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
return validation.ValidateValidatingAdmissionPolicyBinding(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding))
|
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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Canonicalize normalizes the object after validation.
|
// 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.
|
// 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUpdate is the default update validation for an end user.
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
func (ValidatingAdmissionPolicyBindingStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (v *validatingAdmissionPolicyBindingStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
return validation.ValidateValidatingAdmissionPolicyBindingUpdate(obj.(*admissionregistration.ValidatingAdmissionPolicyBinding), old.(*admissionregistration.ValidatingAdmissionPolicyBinding))
|
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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowUnconditionalUpdate is the default update policy for ValidatingAdmissionPolicyBinding objects. Status update should
|
// AllowUnconditionalUpdate is the default update policy for ValidatingAdmissionPolicyBinding objects. Status update should
|
||||||
// only be allowed if version match.
|
// only be allowed if version match.
|
||||||
func (ValidatingAdmissionPolicyBindingStrategy) AllowUnconditionalUpdate() bool {
|
func (v *validatingAdmissionPolicyBindingStrategy) AllowUnconditionalUpdate() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -25,25 +25,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPolicyBindingStrategy(t *testing.T) {
|
func TestPolicyBindingStrategy(t *testing.T) {
|
||||||
|
strategy := NewStrategy(nil, nil, nil)
|
||||||
ctx := genericapirequest.NewDefaultContext()
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
if Strategy.NamespaceScoped() {
|
if strategy.NamespaceScoped() {
|
||||||
t.Error("PolicyBinding strategy must be cluster scoped")
|
t.Error("PolicyBinding strategy must be cluster scoped")
|
||||||
}
|
}
|
||||||
if Strategy.AllowCreateOnUpdate() {
|
if strategy.AllowCreateOnUpdate() {
|
||||||
t.Errorf("PolicyBinding should not allow create on update")
|
t.Errorf("PolicyBinding should not allow create on update")
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration := validPolicyBinding()
|
configuration := validPolicyBinding()
|
||||||
Strategy.PrepareForCreate(ctx, configuration)
|
strategy.PrepareForCreate(ctx, configuration)
|
||||||
errs := Strategy.Validate(ctx, configuration)
|
errs := strategy.Validate(ctx, configuration)
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
t.Errorf("Unexpected error validating %v", errs)
|
t.Errorf("Unexpected error validating %v", errs)
|
||||||
}
|
}
|
||||||
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicyBinding{
|
invalidConfiguration := &admissionregistration.ValidatingAdmissionPolicyBinding{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: ""},
|
ObjectMeta: metav1.ObjectMeta{Name: ""},
|
||||||
}
|
}
|
||||||
Strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
|
strategy.PrepareForUpdate(ctx, invalidConfiguration, configuration)
|
||||||
errs = Strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
|
errs = strategy.ValidateUpdate(ctx, invalidConfiguration, configuration)
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("Expected a validation error")
|
t.Errorf("Expected a validation error")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user