mirror of https://github.com/k3s-io/k3s
Adding the mutating webhook
parent
2aaab817de
commit
ea123f82aa
|
@ -316,7 +316,7 @@ if [[ -n "${GCE_GLBC_IMAGE:-}" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${KUBE_ADMISSION_CONTROL:-}" ]]; then
|
if [[ -z "${KUBE_ADMISSION_CONTROL:-}" ]]; then
|
||||||
ADMISSION_CONTROL="Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,PodPreset,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority"
|
ADMISSION_CONTROL="MutatingAdmissionWebhook,Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,PodPreset,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority"
|
||||||
if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then
|
if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then
|
||||||
ADMISSION_CONTROL="${ADMISSION_CONTROL},PodSecurityPolicy"
|
ADMISSION_CONTROL="${ADMISSION_CONTROL},PodSecurityPolicy"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -104,8 +104,8 @@ func TestAddFlags(t *testing.T) {
|
||||||
MinRequestTimeout: 1800,
|
MinRequestTimeout: 1800,
|
||||||
},
|
},
|
||||||
Admission: &apiserveroptions.AdmissionOptions{
|
Admission: &apiserveroptions.AdmissionOptions{
|
||||||
RecommendedPluginOrder: []string{"NamespaceLifecycle", "Initializers", "GenericAdmissionWebhook"},
|
RecommendedPluginOrder: []string{"MutatingAdmissionWebhook", "NamespaceLifecycle", "Initializers", "GenericAdmissionWebhook"},
|
||||||
DefaultOffPlugins: []string{"Initializers", "GenericAdmissionWebhook"},
|
DefaultOffPlugins: []string{"MutatingAdmissionWebhook", "Initializers", "GenericAdmissionWebhook"},
|
||||||
PluginNames: []string{"AlwaysDeny"},
|
PluginNames: []string{"AlwaysDeny"},
|
||||||
ConfigFile: "/admission-control-config",
|
ConfigFile: "/admission-control-config",
|
||||||
Plugins: s.Admission.Plugins,
|
Plugins: s.Admission.Plugins,
|
||||||
|
|
|
@ -419,7 +419,7 @@ function start_apiserver {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
# Admission Controllers to invoke prior to persisting objects in cluster
|
||||||
ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},DefaultStorageClass,DefaultTolerationSeconds,GenericAdmissionWebhook,ResourceQuota
|
ADMISSION_CONTROL=MutatingAdmissionWebhook,Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},DefaultStorageClass,DefaultTolerationSeconds,GenericAdmissionWebhook,ResourceQuota
|
||||||
# This is the default dir and filename where the apiserver will generate a self-signed cert
|
# This is the default dir and filename where the apiserver will generate a self-signed cert
|
||||||
# which should be able to be used as the CA to verify itself
|
# which should be able to be used as the CA to verify itself
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"admission.go",
|
||||||
|
"doc.go",
|
||||||
|
],
|
||||||
|
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/validating",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
|
"//vendor/k8s.io/api/admission/v1alpha1:go_default_library",
|
||||||
|
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"admission_test.go",
|
||||||
|
"certs_test.go",
|
||||||
|
],
|
||||||
|
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/validating",
|
||||||
|
library = ":go_default_library",
|
||||||
|
deps = [
|
||||||
|
"//vendor/k8s.io/api/admission/v1alpha1:go_default_library",
|
||||||
|
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
|
||||||
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,318 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package mutating delegates admission checks to dynamically configured
|
||||||
|
// mutating webhooks.
|
||||||
|
package mutating
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
admissionv1alpha1 "k8s.io/api/admission/v1alpha1"
|
||||||
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/admission/configuration"
|
||||||
|
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||||
|
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/versioned"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Name of admission plug-in
|
||||||
|
PluginName = "MutatingAdmissionWebhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register registers a plugin
|
||||||
|
func Register(plugins *admission.Plugins) {
|
||||||
|
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
|
||||||
|
plugin, err := NewMutatingWebhook(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugin, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookSource can list dynamic webhook plugins.
|
||||||
|
type WebhookSource interface {
|
||||||
|
Run(stopCh <-chan struct{})
|
||||||
|
Webhooks() (*v1alpha1.MutatingWebhookConfiguration, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMutatingWebhook returns a generic admission webhook plugin.
|
||||||
|
func NewMutatingWebhook(configFile io.Reader) (*MutatingWebhook, error) {
|
||||||
|
kubeconfigFile, err := config.LoadConfig(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cm, err := config.NewClientManager()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authInfoResolver, err := config.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set defaults which may be overridden later.
|
||||||
|
cm.SetAuthenticationInfoResolver(authInfoResolver)
|
||||||
|
cm.SetServiceResolver(config.NewDefaultServiceResolver())
|
||||||
|
|
||||||
|
return &MutatingWebhook{
|
||||||
|
Handler: admission.NewHandler(
|
||||||
|
admission.Connect,
|
||||||
|
admission.Create,
|
||||||
|
admission.Delete,
|
||||||
|
admission.Update,
|
||||||
|
),
|
||||||
|
clientManager: cm,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutatingWebhook is an implementation of admission.Interface.
|
||||||
|
type MutatingWebhook struct {
|
||||||
|
*admission.Handler
|
||||||
|
hookSource WebhookSource
|
||||||
|
namespaceMatcher namespace.Matcher
|
||||||
|
clientManager config.ClientManager
|
||||||
|
convertor versioned.Convertor
|
||||||
|
jsonSerializer runtime.Serializer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = genericadmissioninit.WantsExternalKubeClientSet(&MutatingWebhook{})
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO find a better way wire this, but keep this pull small for now.
|
||||||
|
func (a *MutatingWebhook) SetAuthenticationInfoResolverWrapper(wrapper config.AuthenticationInfoResolverWrapper) {
|
||||||
|
a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
||||||
|
// Passing a nil resolver does not have an effect, instead a default one will be used.
|
||||||
|
func (a *MutatingWebhook) SetServiceResolver(sr config.ServiceResolver) {
|
||||||
|
a.clientManager.SetServiceResolver(sr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
|
||||||
|
func (a *MutatingWebhook) SetScheme(scheme *runtime.Scheme) {
|
||||||
|
if scheme != nil {
|
||||||
|
a.clientManager.SetNegotiatedSerializer(serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{
|
||||||
|
Serializer: serializer.NewCodecFactory(scheme).LegacyCodec(admissionv1alpha1.SchemeGroupVersion),
|
||||||
|
}))
|
||||||
|
a.convertor.Scheme = scheme
|
||||||
|
a.jsonSerializer = json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
|
||||||
|
func (a *MutatingWebhook) SetExternalKubeClientSet(client clientset.Interface) {
|
||||||
|
a.namespaceMatcher.Client = client
|
||||||
|
a.hookSource = configuration.NewMutatingWebhookConfigurationManager(client.AdmissionregistrationV1alpha1().MutatingWebhookConfigurations())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
|
||||||
|
func (a *MutatingWebhook) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||||
|
namespaceInformer := f.Core().V1().Namespaces()
|
||||||
|
a.namespaceMatcher.NamespaceLister = namespaceInformer.Lister()
|
||||||
|
a.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateInitialization implements the InitializationValidator interface.
|
||||||
|
func (a *MutatingWebhook) ValidateInitialization() error {
|
||||||
|
if a.hookSource == nil {
|
||||||
|
return fmt.Errorf("MutatingWebhook admission plugin requires a Kubernetes client to be provided")
|
||||||
|
}
|
||||||
|
if a.jsonSerializer == nil {
|
||||||
|
return fmt.Errorf("MutatingWebhook admission plugin's jsonSerializer is not properly setup")
|
||||||
|
}
|
||||||
|
if err := a.namespaceMatcher.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("MutatingWebhook.namespaceMatcher is not properly setup: %v", err)
|
||||||
|
}
|
||||||
|
if err := a.clientManager.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("MutatingWebhook.clientManager is not properly setup: %v", err)
|
||||||
|
}
|
||||||
|
if err := a.convertor.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("MutatingWebhook.convertor is not properly setup: %v", err)
|
||||||
|
}
|
||||||
|
go a.hookSource.Run(wait.NeverStop)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MutatingWebhook) loadConfiguration(attr admission.Attributes) (*v1alpha1.MutatingWebhookConfiguration, error) {
|
||||||
|
hookConfig, err := a.hookSource.Webhooks()
|
||||||
|
// if Webhook configuration is disabled, fail open
|
||||||
|
if err == configuration.ErrDisabled {
|
||||||
|
return &v1alpha1.MutatingWebhookConfiguration{}, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
e := apierrors.NewServerTimeout(attr.GetResource().GroupResource(), string(attr.GetOperation()), 1)
|
||||||
|
e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the Webhook configuration: %v", err)
|
||||||
|
e.ErrStatus.Reason = "LoadingConfiguration"
|
||||||
|
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||||
|
Type: "MutatingWebhookConfigurationFailure",
|
||||||
|
Message: "An error has occurred while refreshing the MutatingWebhook configuration, no resources can be created/updated/deleted/connected until a refresh succeeds.",
|
||||||
|
})
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return hookConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit makes an admission decision based on the request attributes.
|
||||||
|
func (a *MutatingWebhook) Admit(attr admission.Attributes) error {
|
||||||
|
hookConfig, err := a.loadConfiguration(attr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hooks := hookConfig.Webhooks
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
var relevantHooks []*v1alpha1.Webhook
|
||||||
|
for i := range hooks {
|
||||||
|
call, err := a.shouldCallHook(&hooks[i], attr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if call {
|
||||||
|
relevantHooks = append(relevantHooks, &hooks[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(relevantHooks) == 0 {
|
||||||
|
// no matching hooks
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the object to the external version before sending it to the webhook
|
||||||
|
versionedAttr := versioned.Attributes{
|
||||||
|
Attributes: attr,
|
||||||
|
}
|
||||||
|
if oldObj := attr.GetOldObject(); oldObj != nil {
|
||||||
|
out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind())
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
versionedAttr.OldObject = out
|
||||||
|
}
|
||||||
|
if obj := attr.GetObject(); obj != nil {
|
||||||
|
out, err := a.convertor.ConvertToGVK(obj, attr.GetKind())
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
versionedAttr.Object = out
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hook := range relevantHooks {
|
||||||
|
t := time.Now()
|
||||||
|
err := a.callAttrMutatingHook(ctx, hook, versionedAttr)
|
||||||
|
admission.Metrics.ObserveWebhook(time.Since(t), err != nil, hook, attr)
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1alpha1.Ignore
|
||||||
|
if callErr, ok := err.(*webhookerrors.ErrCallingWebhook); ok {
|
||||||
|
if ignoreClientCallFailures {
|
||||||
|
glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||||
|
utilruntime.HandleError(callErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
glog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||||
|
}
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert attr.Object to the internal version
|
||||||
|
return a.convertor.Convert(versionedAttr.Object, attr.GetObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: factor into a common place along with the validating webhook version.
|
||||||
|
func (a *MutatingWebhook) shouldCallHook(h *v1alpha1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||||
|
var matches bool
|
||||||
|
for _, r := range h.Rules {
|
||||||
|
m := rules.Matcher{Rule: r, Attr: attr}
|
||||||
|
if m.Matches() {
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matches {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.namespaceMatcher.MatchNamespaceSelector(h, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// note that callAttrMutatingHook updates attr
|
||||||
|
func (a *MutatingWebhook) callAttrMutatingHook(ctx context.Context, h *v1alpha1.Webhook, attr versioned.Attributes) error {
|
||||||
|
// Make the webhook request
|
||||||
|
request := request.CreateAdmissionReview(attr)
|
||||||
|
client, err := a.clientManager.HookClient(h)
|
||||||
|
if err != nil {
|
||||||
|
return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||||
|
}
|
||||||
|
response := &admissionv1alpha1.AdmissionReview{}
|
||||||
|
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
|
||||||
|
return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.Response.Allowed {
|
||||||
|
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchJS := response.Response.Patch
|
||||||
|
if len(patchJS) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
patchObj, err := jsonpatch.DecodePatch(patchJS)
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
objJS, err := runtime.Encode(a.jsonSerializer, attr.Object)
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
patchedJS, err := patchObj.Apply(objJS)
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
// TODO: if we have multiple mutating webhooks, we can remember the json
|
||||||
|
// instead of encoding and decoding for each one.
|
||||||
|
if _, _, err := a.jsonSerializer.Decode(patchedJS, nil, attr.Object); err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,649 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package mutating
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/api/admission/v1alpha1"
|
||||||
|
registrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/testdata"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeHookSource struct {
|
||||||
|
hooks []registrationv1alpha1.Webhook
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeHookSource) Webhooks() (*registrationv1alpha1.MutatingWebhookConfiguration, error) {
|
||||||
|
if f.err != nil {
|
||||||
|
return nil, f.err
|
||||||
|
}
|
||||||
|
for i, h := range f.hooks {
|
||||||
|
if h.NamespaceSelector == nil {
|
||||||
|
f.hooks[i].NamespaceSelector = &metav1.LabelSelector{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ®istrationv1alpha1.MutatingWebhookConfiguration{Webhooks: f.hooks}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeHookSource) Run(stopCh <-chan struct{}) {}
|
||||||
|
|
||||||
|
type fakeServiceResolver struct {
|
||||||
|
base url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
|
||||||
|
if namespace == "failResolve" {
|
||||||
|
return nil, fmt.Errorf("couldn't resolve service location")
|
||||||
|
}
|
||||||
|
u := f.base
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeNamespaceLister struct {
|
||||||
|
namespaces map[string]*corev1.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) {
|
||||||
|
ns, ok := f.namespaces[name]
|
||||||
|
if ok {
|
||||||
|
return ns, nil
|
||||||
|
}
|
||||||
|
return nil, errors.NewNotFound(corev1.Resource("namespaces"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ccfgSVC returns a client config using the service reference mechanism.
|
||||||
|
func ccfgSVC(urlPath string) registrationv1alpha1.WebhookClientConfig {
|
||||||
|
return registrationv1alpha1.WebhookClientConfig{
|
||||||
|
Service: ®istrationv1alpha1.ServiceReference{
|
||||||
|
Name: "webhook-test",
|
||||||
|
Namespace: "default",
|
||||||
|
Path: &urlPath,
|
||||||
|
},
|
||||||
|
CABundle: testdata.CACert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type urlConfigGenerator struct {
|
||||||
|
baseURL *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// ccfgURL returns a client config using the URL mechanism.
|
||||||
|
func (c urlConfigGenerator) ccfgURL(urlPath string) registrationv1alpha1.WebhookClientConfig {
|
||||||
|
u2 := *c.baseURL
|
||||||
|
u2.Path = urlPath
|
||||||
|
urlString := u2.String()
|
||||||
|
return registrationv1alpha1.WebhookClientConfig{
|
||||||
|
URL: &urlString,
|
||||||
|
CABundle: testdata.CACert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAdmit tests that MutatingWebhook#Admit works as expected
|
||||||
|
func TestAdmit(t *testing.T) {
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
v1alpha1.AddToScheme(scheme)
|
||||||
|
corev1.AddToScheme(scheme)
|
||||||
|
|
||||||
|
testServer := newTestServer(t)
|
||||||
|
testServer.StartTLS()
|
||||||
|
defer testServer.Close()
|
||||||
|
serverURL, err := url.ParseRequestURI(testServer.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("this should never happen? %v", err)
|
||||||
|
}
|
||||||
|
wh, err := NewMutatingWebhook(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cm, err := config.NewClientManager()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create client manager: %v", err)
|
||||||
|
}
|
||||||
|
cm.SetAuthenticationInfoResolver(newFakeAuthenticationInfoResolver(new(int32)))
|
||||||
|
cm.SetServiceResolver(fakeServiceResolver{base: *serverURL})
|
||||||
|
wh.clientManager = cm
|
||||||
|
wh.SetScheme(scheme)
|
||||||
|
if err = wh.clientManager.Validate(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
namespace := "webhook-test"
|
||||||
|
wh.namespaceMatcher.NamespaceLister = fakeNamespaceLister{map[string]*corev1.Namespace{
|
||||||
|
namespace: {
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"runlevel": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up a test object for the call
|
||||||
|
kind := corev1.SchemeGroupVersion.WithKind("Pod")
|
||||||
|
name := "my-pod"
|
||||||
|
object := corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"pod.name": name,
|
||||||
|
},
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
oldObject := corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||||
|
}
|
||||||
|
operation := admission.Update
|
||||||
|
resource := corev1.Resource("pods").WithVersion("v1")
|
||||||
|
subResource := ""
|
||||||
|
userInfo := user.DefaultInfo{
|
||||||
|
Name: "webhook-test",
|
||||||
|
UID: "webhook-test",
|
||||||
|
}
|
||||||
|
|
||||||
|
ccfgURL := urlConfigGenerator{serverURL}.ccfgURL
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
hookSource fakeHookSource
|
||||||
|
path string
|
||||||
|
expectAllow bool
|
||||||
|
errorContains string
|
||||||
|
}
|
||||||
|
|
||||||
|
matchEverythingRules := []registrationv1alpha1.RuleWithOperations{{
|
||||||
|
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll},
|
||||||
|
Rule: registrationv1alpha1.Rule{
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
APIVersions: []string{"*"},
|
||||||
|
Resources: []string{"*/*"},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
policyFail := registrationv1alpha1.Fail
|
||||||
|
policyIgnore := registrationv1alpha1.Ignore
|
||||||
|
|
||||||
|
table := map[string]test{
|
||||||
|
"no match": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "nomatch",
|
||||||
|
ClientConfig: ccfgSVC("disallow"),
|
||||||
|
Rules: []registrationv1alpha1.RuleWithOperations{{
|
||||||
|
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
},
|
||||||
|
"match & allow": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "allow",
|
||||||
|
ClientConfig: ccfgSVC("allow"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
},
|
||||||
|
"match & disallow": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "disallow",
|
||||||
|
ClientConfig: ccfgSVC("disallow"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
errorContains: "without explanation",
|
||||||
|
},
|
||||||
|
"match & disallow ii": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "disallowReason",
|
||||||
|
ClientConfig: ccfgSVC("disallowReason"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
errorContains: "you shall not pass",
|
||||||
|
},
|
||||||
|
"match & disallow & but allowed because namespaceSelector exempt the namespace": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "disallow",
|
||||||
|
ClientConfig: ccfgSVC("disallow"),
|
||||||
|
Rules: newMatchEverythingRules(),
|
||||||
|
NamespaceSelector: &metav1.LabelSelector{
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{{
|
||||||
|
Key: "runlevel",
|
||||||
|
Values: []string{"1"},
|
||||||
|
Operator: metav1.LabelSelectorOpIn,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
},
|
||||||
|
"match & disallow & but allowed because namespaceSelector exempt the namespace ii": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "disallow",
|
||||||
|
ClientConfig: ccfgSVC("disallow"),
|
||||||
|
Rules: newMatchEverythingRules(),
|
||||||
|
NamespaceSelector: &metav1.LabelSelector{
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{{
|
||||||
|
Key: "runlevel",
|
||||||
|
Values: []string{"0"},
|
||||||
|
Operator: metav1.LabelSelectorOpNotIn,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
},
|
||||||
|
"match & fail (but allow because fail open)": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "internalErr A",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}, {
|
||||||
|
Name: "internalErr B",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}, {
|
||||||
|
Name: "internalErr C",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
},
|
||||||
|
"match & fail (but disallow because fail closed on nil)": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "internalErr A",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}, {
|
||||||
|
Name: "internalErr B",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}, {
|
||||||
|
Name: "internalErr C",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: false,
|
||||||
|
},
|
||||||
|
"match & fail (but fail because fail closed)": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "internalErr A",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
FailurePolicy: &policyFail,
|
||||||
|
}, {
|
||||||
|
Name: "internalErr B",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
FailurePolicy: &policyFail,
|
||||||
|
}, {
|
||||||
|
Name: "internalErr C",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
FailurePolicy: &policyFail,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: false,
|
||||||
|
},
|
||||||
|
"match & allow (url)": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "allow",
|
||||||
|
ClientConfig: ccfgURL("allow"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
},
|
||||||
|
"match & disallow (url)": {
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "disallow",
|
||||||
|
ClientConfig: ccfgURL("disallow"),
|
||||||
|
Rules: matchEverythingRules,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
errorContains: "without explanation",
|
||||||
|
},
|
||||||
|
// No need to test everything with the url case, since only the
|
||||||
|
// connection is different.
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range table {
|
||||||
|
if !strings.Contains(name, "no match") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
wh.hookSource = &tt.hookSource
|
||||||
|
err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo))
|
||||||
|
if tt.expectAllow != (err == nil) {
|
||||||
|
t.Errorf("expected allowed=%v, but got err=%v", tt.expectAllow, err)
|
||||||
|
}
|
||||||
|
// ErrWebhookRejected is not an error for our purposes
|
||||||
|
if tt.errorContains != "" {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tt.errorContains) {
|
||||||
|
t.Errorf(" expected an error saying %q, but got %v", tt.errorContains, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, isStatusErr := err.(*apierrors.StatusError); err != nil && !isStatusErr {
|
||||||
|
t.Errorf("%s: expected a StatusError, got %T", name, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAdmitCachedClient tests that MutatingWebhook#Admit should cache restClient
|
||||||
|
func TestAdmitCachedClient(t *testing.T) {
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
v1alpha1.AddToScheme(scheme)
|
||||||
|
corev1.AddToScheme(scheme)
|
||||||
|
|
||||||
|
testServer := newTestServer(t)
|
||||||
|
testServer.StartTLS()
|
||||||
|
defer testServer.Close()
|
||||||
|
serverURL, err := url.ParseRequestURI(testServer.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("this should never happen? %v", err)
|
||||||
|
}
|
||||||
|
wh, err := NewMutatingWebhook(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cm, err := config.NewClientManager()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create client manager: %v", err)
|
||||||
|
}
|
||||||
|
cm.SetServiceResolver(fakeServiceResolver{base: *serverURL})
|
||||||
|
wh.clientManager = cm
|
||||||
|
wh.SetScheme(scheme)
|
||||||
|
namespace := "webhook-test"
|
||||||
|
wh.namespaceMatcher.NamespaceLister = fakeNamespaceLister{map[string]*corev1.Namespace{
|
||||||
|
namespace: {
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"runlevel": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up a test object for the call
|
||||||
|
kind := corev1.SchemeGroupVersion.WithKind("Pod")
|
||||||
|
name := "my-pod"
|
||||||
|
object := corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"pod.name": name,
|
||||||
|
},
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
oldObject := corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||||
|
}
|
||||||
|
operation := admission.Update
|
||||||
|
resource := corev1.Resource("pods").WithVersion("v1")
|
||||||
|
subResource := ""
|
||||||
|
userInfo := user.DefaultInfo{
|
||||||
|
Name: "webhook-test",
|
||||||
|
UID: "webhook-test",
|
||||||
|
}
|
||||||
|
ccfgURL := urlConfigGenerator{serverURL}.ccfgURL
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
hookSource fakeHookSource
|
||||||
|
expectAllow bool
|
||||||
|
expectCache bool
|
||||||
|
}
|
||||||
|
|
||||||
|
policyIgnore := registrationv1alpha1.Ignore
|
||||||
|
cases := []test{
|
||||||
|
{
|
||||||
|
name: "cache 1",
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "cache1",
|
||||||
|
ClientConfig: ccfgSVC("allow"),
|
||||||
|
Rules: newMatchEverythingRules(),
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
expectCache: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cache 2",
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "cache2",
|
||||||
|
ClientConfig: ccfgSVC("internalErr"),
|
||||||
|
Rules: newMatchEverythingRules(),
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
expectCache: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cache 3",
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "cache3",
|
||||||
|
ClientConfig: ccfgSVC("allow"),
|
||||||
|
Rules: newMatchEverythingRules(),
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
expectCache: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cache 4",
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "cache4",
|
||||||
|
ClientConfig: ccfgURL("allow"),
|
||||||
|
Rules: newMatchEverythingRules(),
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
expectCache: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cache 5",
|
||||||
|
hookSource: fakeHookSource{
|
||||||
|
hooks: []registrationv1alpha1.Webhook{{
|
||||||
|
Name: "cache5",
|
||||||
|
ClientConfig: ccfgURL("allow"),
|
||||||
|
Rules: newMatchEverythingRules(),
|
||||||
|
FailurePolicy: &policyIgnore,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectAllow: true,
|
||||||
|
expectCache: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testcase := range cases {
|
||||||
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
wh.hookSource = &testcase.hookSource
|
||||||
|
authInfoResolverCount := new(int32)
|
||||||
|
r := newFakeAuthenticationInfoResolver(authInfoResolverCount)
|
||||||
|
wh.clientManager.SetAuthenticationInfoResolver(r)
|
||||||
|
if err = wh.clientManager.Validate(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, testcase.name, resource, subResource, operation, &userInfo))
|
||||||
|
if testcase.expectAllow != (err == nil) {
|
||||||
|
t.Errorf("expected allowed=%v, but got err=%v", testcase.expectAllow, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testcase.expectCache && *authInfoResolverCount != 1 {
|
||||||
|
t.Errorf("expected cacheclient, but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testcase.expectCache && *authInfoResolverCount != 0 {
|
||||||
|
t.Errorf("expected not cacheclient, but got cache")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestServer(t *testing.T) *httptest.Server {
|
||||||
|
// Create the test webhook server
|
||||||
|
sCert, err := tls.X509KeyPair(testdata.ServerCert, testdata.ServerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rootCAs := x509.NewCertPool()
|
||||||
|
rootCAs.AppendCertsFromPEM(testdata.CACert)
|
||||||
|
testServer := httptest.NewUnstartedServer(http.HandlerFunc(webhookHandler))
|
||||||
|
testServer.TLS = &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{sCert},
|
||||||
|
ClientCAs: rootCAs,
|
||||||
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||||
|
}
|
||||||
|
return testServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func webhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("got req: %v\n", r.URL.Path)
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/internalErr":
|
||||||
|
http.Error(w, "webhook internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
case "/invalidReq":
|
||||||
|
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||||
|
w.Write([]byte("webhook invalid request"))
|
||||||
|
return
|
||||||
|
case "/invalidResp":
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte("webhook invalid response"))
|
||||||
|
case "/disallow":
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{
|
||||||
|
Response: &v1alpha1.AdmissionResponse{
|
||||||
|
Allowed: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case "/disallowReason":
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{
|
||||||
|
Response: &v1alpha1.AdmissionResponse{
|
||||||
|
Allowed: false,
|
||||||
|
Result: &metav1.Status{
|
||||||
|
Message: "you shall not pass",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case "/allow":
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{
|
||||||
|
Response: &v1alpha1.AdmissionResponse{
|
||||||
|
Allowed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeAuthenticationInfoResolver(count *int32) *fakeAuthenticationInfoResolver {
|
||||||
|
return &fakeAuthenticationInfoResolver{
|
||||||
|
restConfig: &rest.Config{
|
||||||
|
TLSClientConfig: rest.TLSClientConfig{
|
||||||
|
CAData: testdata.CACert,
|
||||||
|
CertData: testdata.ClientCert,
|
||||||
|
KeyData: testdata.ClientKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cachedCount: count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeAuthenticationInfoResolver struct {
|
||||||
|
restConfig *rest.Config
|
||||||
|
cachedCount *int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) {
|
||||||
|
atomic.AddInt32(c.cachedCount, 1)
|
||||||
|
return c.restConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMatchEverythingRules() []registrationv1alpha1.RuleWithOperations {
|
||||||
|
return []registrationv1alpha1.RuleWithOperations{{
|
||||||
|
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll},
|
||||||
|
Rule: registrationv1alpha1.Rule{
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
APIVersions: []string{"*"},
|
||||||
|
Resources: []string{"*/*"},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package mutating makes calls to mutating webhooks during the admission
|
||||||
|
// process.
|
||||||
|
package mutating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
|
216
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testdata/certs.go
vendored
Normal file
216
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testdata/certs.go
vendored
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file was generated using openssl by the gencerts.sh script
|
||||||
|
// and holds raw certificates for the webhook tests.
|
||||||
|
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
var CAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEArqnW4K+UsmPzbSB7JYhN0HNsJNItjw/87SJxIjGqUttC+2ts
|
||||||
|
+Y+BddcQgO0EfzP68QJ6+H7itmzdqWPTfoZJiuy+twXxgFwMH2WCKB6I5CgnVHcD
|
||||||
|
acmdP7vOgr1GcyqIV16NWEnt6hxRNfVnerduLWWfVr0/wGY3ajw06FHEx7oL1Jfx
|
||||||
|
1lo4UPVdp39Pt5rOww66FP8etRaEhy2AHEtfHS4L4GxcJL/0n3w8UIfMqYjTKW9l
|
||||||
|
zuKXUurz9HEJ3N+JMItolf+3ohMp/xciiSBvHzFbfu/Zv2nXqWGcxqOmnM1L8ZLw
|
||||||
|
Q4c3ZRQ/n9tIacEZyy69VASAQcIjMdwIHcWZLwIDAQABAoIBADybeJGMu6dPIYfr
|
||||||
|
bm/upTnA43S/bcmnvZc3jVRVMYoAxXRiqXTLhBu03egu1pGhIuGAf9U8ikTM7/m4
|
||||||
|
RwovZNONJPxzVoK47gfy/EAZoFyzRjp79bY+nI8iBx28ufZ6esb+a0OIm8LRwqhb
|
||||||
|
mGWvws6D5c9+aeHEVlRJwf4faY34ASEbw19QhOLfPCGp0wOy4MX3aIMaCfZ+iHYc
|
||||||
|
GAVaf44rWmTYKHEkLMABky9jGIXXJXROY/ggKWC1zXdPVChO40ECd1b2XGry6Ta/
|
||||||
|
j+quFXDgI7b/ju/7jLnDCQjuC5G6E7X3n8KLZtzJrReiwpyeFo86GWc5E99umOyB
|
||||||
|
tPjwNikCgYEA1mbNAVg3mBOFcEAN0j5QKEy1hv16nVlGRwBye/0nG3mUHCs+UzC2
|
||||||
|
fQyDfPcfXZERyDb4QJCJ4Y6VSVoo3Jn1okFPbz7y2eKdjo1N65T9HrK7G+QZXinH
|
||||||
|
/72LktfAWphdz2JKuSnrlJS8YupSx7pS+lpz+W+rcDpdYVJXoD6L6SMCgYEA0I1E
|
||||||
|
4h3MH5O46GJvMoGW4PH5FIi5nbWj69Y4/nRJCACNlLPJ+k3lcf0OTYcy9pllv5Ya
|
||||||
|
EV5n0qHAH7ACudKoB6YqvDsrZxfv8tlmWLBTFp5QQpBdlMWjgGSbJLbkxvt3rUfF
|
||||||
|
x/eQebvzSqp69R0/XqJ9fxWXvdtZoZYXNJxVPoUCgYAgA7W077FNegzA2C+4Jync
|
||||||
|
+qdYgt0eRchircRqk0CVr6/YDPT/gxSc05OGw3fhhtn65YpoSaztC1drXpUfa7Xs
|
||||||
|
BoiP+fxVYKtaL+tktBifztx1q7fGAcMlgu4mfSTx4jKP1wOFZqcQxqzisE6wGDhv
|
||||||
|
vbX3lx8oYO60q5D+EpjdtQKBgDM/A3YsrEP2ILG5vmlCvrh3vST2k+XVBHqnIUol
|
||||||
|
eOymdiPcKf1/tqnT7PfQCQ3fk8kIMU+jSw/O/07KCWFwCioXAtlOENQ8ZZHfKe8R
|
||||||
|
JNmh/UbeAqDUD+E014qmBoF+uWGzCT6h7rZ7IMVwLtacYT33366it67Hf7bdEsay
|
||||||
|
w5+hAoGABSgjlf9WsC7WzY6sZwZG25aBMGFw6vr3jawLiuNk3Hr+pGV/H7PEzSh+
|
||||||
|
vBpvC0Vkp5Dg32asmME40LbYpMu2BV4E1wK17i+DZVUMezNO0mABykWecyPYdmxL
|
||||||
|
bJtLu4yaP84W433T5E6G7Im+x+KjXI7TRzpQZFQnVadmmpuurUY=
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var CACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDPTCCAiWgAwIBAgIJALTyMgMR6YygMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
||||||
|
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
||||||
|
DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjA0MTIwMAYDVQQDDClnZW5l
|
||||||
|
cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggEPADCCAQoCggEBAK6p1uCvlLJj820geyWITdBzbCTSLY8P/O0i
|
||||||
|
cSIxqlLbQvtrbPmPgXXXEIDtBH8z+vECevh+4rZs3alj036GSYrsvrcF8YBcDB9l
|
||||||
|
gigeiOQoJ1R3A2nJnT+7zoK9RnMqiFdejVhJ7eocUTX1Z3q3bi1ln1a9P8BmN2o8
|
||||||
|
NOhRxMe6C9SX8dZaOFD1Xad/T7eazsMOuhT/HrUWhIctgBxLXx0uC+BsXCS/9J98
|
||||||
|
PFCHzKmI0ylvZc7il1Lq8/RxCdzfiTCLaJX/t6ITKf8XIokgbx8xW37v2b9p16lh
|
||||||
|
nMajppzNS/GS8EOHN2UUP5/bSGnBGcsuvVQEgEHCIzHcCB3FmS8CAwEAAaNQME4w
|
||||||
|
HQYDVR0OBBYEFBsZKbynr9Iix+ud0FxQvMVIPZqOMB8GA1UdIwQYMBaAFBsZKbyn
|
||||||
|
r9Iix+ud0FxQvMVIPZqOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
|
||||||
|
AJZXfcHyAsq5qr1UqhutrNlsW2u7kkAc+Ql5wZEdXIyjKKC+kOllWqKo5IPtmMIi
|
||||||
|
R5VCm1g3iFCUV6FdXNtfF7tWZqaHV58nkJYlDc2yZxQzaWQeu8U92w+Qr5H1ansL
|
||||||
|
FOGS6A4+rMj2EDEt+lCmsz+l5UD7nrhnyGMzeumASQ6cXPV1uB2LTc4IzsOYFKs9
|
||||||
|
nt9SDH7tF+0bQwZ1YUrfMYJpNp6ETjpPKJVhq8/FGqwT+9egFbgjAhrEpPccFkXo
|
||||||
|
D7NLhM1JHUiqNQStDq9mDKLHKmp++UScrNc66b6egN1sIPBHyLu8ApzcF2YHuEYC
|
||||||
|
RGyWZ0sJtjzWjK7IU9RdmLI=
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var BadCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEArgYoZAUDj1/dQ2G+dMib+b1m39ACK6wBt4zDjl8x5iUZZjDy
|
||||||
|
O1l/Hj6n3NUHphFsYZ5070ds8GwXANvYGFi3F6GFiRW/R5lcChmhxhY5JJtWquDp
|
||||||
|
amonNEjiAuiFf6u6CKeBvm8CgcH/Cbzc4XP1oJZiSaGOpmqGiHlLeL25y6ZR3X7D
|
||||||
|
jOPalSi9WHQN0m9DS7EMvyalySAwzJKlcOpSeBPdMTp1Ay1HShJiiNj8sdCRLpq7
|
||||||
|
rwHypKS6mVNItsAnx+jc40d162dmFQg8FBm3M6d5QTrJBAfuvoLrlNCtssl11hOi
|
||||||
|
XDYJshTBVI9HAR/lzQiF+coZOHJwYuYXp7xYOwIDAQABAoIBAQChzSvkwxyqQ9Gw
|
||||||
|
AsNYReVv8IAj/HzoKgd2p7RzPWNhvoC9GSk/sViVwF/G3XM9HtoMcY37pAdQCs/g
|
||||||
|
hoeHK4UgvZcw/D1azuZapbZaPPNoa93LB08/F+/XlyQ83ACz0fEodsYVT5WfG8aL
|
||||||
|
QUSFgpGQfAJqv4GojUcEwPJBEvYat8I028fzYMlLJ9m45pQFzFsGKU36vs6esPkL
|
||||||
|
MKbVO1qI6CEVDtnLkIo2bE5vpo8w5C22HseFO+E+1VNaHIRK5TBBGGWVJVfUZ3bq
|
||||||
|
7LWngkaN9gspCauKkTozj2bl611lFRI7wbA32WV1eIYgNT+8jTESo/oHpsnpRSI7
|
||||||
|
4UMp5GExAoGBAN7wkIS4zWGF2hijsorcPLBx3YOsBxW26Qhl/ck6a3lVGZHrDijc
|
||||||
|
u8hDhOWNDxSSqUwQiL7UAVcE7npw6XpZ37Obc//t+Hm/gGUOIGlB/Pl3g4h7pF7T
|
||||||
|
s2pXIMKvF0dfQGpmqgCytUz7Oho4LLbkywky7jykMc8IlVuTZEdKbdZHAoGBAMfU
|
||||||
|
nR+79gT8yIBruEX3VI71Vbce0Pn+3+G+PO12uUN6XqMb9YA+f5aS6AwP5EupERwn
|
||||||
|
YvkMkCNSYYkV+GU5b+N9Pn7xt33dnEqhrGPUrOLoIAl6qJ7jc78GZy67SCuIKrZZ
|
||||||
|
AN5qFQlRCENv28C+0Ne6rMX+8/JL1Mxo+0J/6QRtAoGAbNhs5q/Hbm7IfbEmkY9X
|
||||||
|
fhoJuai6yMpF2hjZoG6KXHHFCy4E+sRSVkNI1j5Zd4TnbUDBUtH1WYQJ3vPTui24
|
||||||
|
/1rNds27u81YpX4RKvLRzQahzHf5V2bquOeTEhokNm915rz7EV4vEEe0JWr5wc3Q
|
||||||
|
p0wbbrYHr3oUWeKLWhcnqy8CgYEAh9XiHMFDIe7HSGxw7baLl0Xzxy++dEGp5CTR
|
||||||
|
+8VZeCIFlLCbuFpDlpI0BIcE891wEQhBAfRlQm1seagimoRpp2Tqh5Y92eQ7qout
|
||||||
|
yIq4HuIVbPwhBSit9Gsg1qZeD6FXD27+5TGNLTEVAepWofXTtuFhMpH1N34OoAi4
|
||||||
|
y2Jxfh0CgYB4IrPUeBAZKiC6Lo6nwxo0rrsiHLJwXJojLwsUt0zG5vh3mI25KAB1
|
||||||
|
a3ARRbiRKU/IX5I9ToclJ3h0a1nVr1kzV/E5f+5FgQ9swkTNEbM8gBsc5X9ZayjD
|
||||||
|
Hfv6+p7TH3bReXDKtpOUgso0dIy2anN6Ppu1wODtrFnUOJ9wkO4OSg==
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var BadCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDPTCCAiWgAwIBAgIJAIaoBDrksTyaMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
||||||
|
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
||||||
|
DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjA0MTIwMAYDVQQDDClnZW5l
|
||||||
|
cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggEPADCCAQoCggEBAK4GKGQFA49f3UNhvnTIm/m9Zt/QAiusAbeM
|
||||||
|
w45fMeYlGWYw8jtZfx4+p9zVB6YRbGGedO9HbPBsFwDb2BhYtxehhYkVv0eZXAoZ
|
||||||
|
ocYWOSSbVqrg6WpqJzRI4gLohX+rugingb5vAoHB/wm83OFz9aCWYkmhjqZqhoh5
|
||||||
|
S3i9ucumUd1+w4zj2pUovVh0DdJvQ0uxDL8mpckgMMySpXDqUngT3TE6dQMtR0oS
|
||||||
|
YojY/LHQkS6au68B8qSkuplTSLbAJ8fo3ONHdetnZhUIPBQZtzOneUE6yQQH7r6C
|
||||||
|
65TQrbLJddYTolw2CbIUwVSPRwEf5c0IhfnKGThycGLmF6e8WDsCAwEAAaNQME4w
|
||||||
|
HQYDVR0OBBYEFFFthspVCOb5fSkQ2BFCykech3RVMB8GA1UdIwQYMBaAFFFthspV
|
||||||
|
COb5fSkQ2BFCykech3RVMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
|
||||||
|
AEgGbcx1qhdi4lFNC0YRHJxjn3JPW6tr4qgDiusqMj9TF9/RohKOvLblq2kSB0x3
|
||||||
|
pyDMkVv2rd5U4qtKruEQ1OgY3cB7hy6mt/ZhldF540Lli8j9N63LMRXwIu068j2W
|
||||||
|
WSiWV416LOZEcuid7mZjAsbG4xvaDg/yW1RBpA3XnwMSmr7Y+T6XkjzgT3WWiwOf
|
||||||
|
4ANc3ecsl53x/beb9YF+TjqmjmtGSgUW78UTAsGFFKmjJ/cStQUaMCEvS9Gun7hH
|
||||||
|
eLarZIVV5Ia/FziGHoi7Q44C66pXD437xmkR1ueExoKwXbBt4c5GeH1rJjUVnlyk
|
||||||
|
pMokZBC57nXx8krZVEu1SRA=
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var ServerKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEA13f50PPWuR/InxLIoJjHdNSG+jVUd25CY7ZL2J023X2BAY+1
|
||||||
|
M6jkLR6C2nSFZnn58ubiB74/d1g/Fg1Twd419iR615A013f+qOoyFx3LFHxU1S6e
|
||||||
|
v22fgJ6ntK/+4QD5MwNgOwD8k1jN2WxHqNWn16IF4Tidbv8M9A35YHAdtYDYaOJC
|
||||||
|
kzjVztzRw1y6bKRakpMXxHylQyWmAKDJ2GSbRTbGtjr7Ji54WBfG43k94tO5X8K4
|
||||||
|
VGbz/uxrKe1IFMHNOlrjR438dbOXusksx9EIqDA9a42J3qjr5NKSqzCIbgBFl6qu
|
||||||
|
45V3A7cdRI/sJ2G1aqlWIXh2fAQiaFQAEBrPfwIDAQABAoIBAAZbxgWCjJ2d8H+x
|
||||||
|
QDZtC8XI18redAWqPU9P++ECkrHqmDoBkalanJEwS1BDDATAKL4gTh9IX/sXoZT3
|
||||||
|
A7e+5PzEitN9r/GD2wIFF0FTYcDTAnXgEFM52vEivXQ5lV3yd2gn+1kCaHG4typp
|
||||||
|
ZZv34iIc5+uDjjHOWQWCvA86f8XxX5EfYH+GkjfixTtN2xhWWlfi9vzYeESS4Jbt
|
||||||
|
tqfH0iEaZ1Bm/qvb8vFgKiuSTOoSpaf+ojAdtPtXDjf1bBtQQG+RSQkP59O/taLM
|
||||||
|
FCVuRrU8EtdB0+9anwmAP+O2UqjL5izA578lQtdIh13jHtGEgOcnfGNUphK11y9r
|
||||||
|
Mg5V28ECgYEA9fwI6Xy1Rb9b9irp4bU5Ec99QXa4x2bxld5cDdNOZWJQu9OnaIbg
|
||||||
|
kw/1SyUkZZCGMmibM/BiWGKWoDf8E+rn/ujGOtd70sR9U0A94XMPqEv7iHxhpZmD
|
||||||
|
rZuSz4/snYbOWCZQYXFoD/nqOwE7Atnz7yh+Jti0qxBQ9bmkb9o0QW8CgYEA4D3d
|
||||||
|
okzodg5QQ1y9L0J6jIC6YysoDedveYZMd4Un9bKlZEJev4OwiT4xXmSGBYq/7dzo
|
||||||
|
OJOvN6qgPfibr27mSB8NkAk6jL/VdJf3thWxNYmjF4E3paLJ24X31aSipN1Ta6K3
|
||||||
|
KKQUQRvixVoI1q+8WHAubBDEqvFnNYRHD+AjKvECgYBkekjhpvEcxme4DBtw+OeQ
|
||||||
|
4OJXJTmhKemwwB12AERboWc88d3GEqIVMEWQJmHRotFOMfCDrMNfOxYv5+5t7FxL
|
||||||
|
gaXHT1Hi7CQNJ4afWrKgmjjqrXPtguGIvq2fXzjVt8T9uNjIlNxe+kS1SXFjXsgH
|
||||||
|
ftDY6VgTMB0B4ozKq6UAvQKBgQDER8K5buJHe+3rmMCMHn+Qfpkndr4ftYXQ9Kn4
|
||||||
|
MFiy6sV0hdfTgRzEdOjXu9vH/BRVy3iFFVhYvIR42iTEIal2VaAUhM94Je5cmSyd
|
||||||
|
eE1eFHTqfRPNazmPaqttmSc4cfa0D4CNFVoZR6RupIl6Cect7jvkIaVUD+wMXxWo
|
||||||
|
osOFsQKBgDLwVhZWoQ13RV/jfQxS3veBUnHJwQJ7gKlL1XZ16mpfEOOVnJF7Es8j
|
||||||
|
TIIXXYhgSy/XshUbsgXQ+YGliye/rXSCTXHBXvWShOqxEMgeMYMRkcm8ZLp/DH7C
|
||||||
|
kC2pemkLPUJqgSh1PASGcJbDJIvFGUfP69tUCYpHpk3nHzexuAg3
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var ServerCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDQDCCAiigAwIBAgIJANWw74P5KJk2MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
||||||
|
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
||||||
|
DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjAjMSEwHwYDVQQDExh3ZWJo
|
||||||
|
b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||||
|
AoIBAQDXd/nQ89a5H8ifEsigmMd01Ib6NVR3bkJjtkvYnTbdfYEBj7UzqOQtHoLa
|
||||||
|
dIVmefny5uIHvj93WD8WDVPB3jX2JHrXkDTXd/6o6jIXHcsUfFTVLp6/bZ+Anqe0
|
||||||
|
r/7hAPkzA2A7APyTWM3ZbEeo1afXogXhOJ1u/wz0DflgcB21gNho4kKTONXO3NHD
|
||||||
|
XLpspFqSkxfEfKVDJaYAoMnYZJtFNsa2OvsmLnhYF8bjeT3i07lfwrhUZvP+7Gsp
|
||||||
|
7UgUwc06WuNHjfx1s5e6ySzH0QioMD1rjYneqOvk0pKrMIhuAEWXqq7jlXcDtx1E
|
||||||
|
j+wnYbVqqVYheHZ8BCJoVAAQGs9/AgMBAAGjZDBiMAkGA1UdEwQCMAAwCwYDVR0P
|
||||||
|
BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATApBgNVHREEIjAg
|
||||||
|
hwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVsdC5zdmMwDQYJKoZIhvcNAQELBQAD
|
||||||
|
ggEBAD/GKSPNyQuAOw/jsYZesb+RMedbkzs18sSwlxAJQMUrrXwlVdHrA8q5WhE6
|
||||||
|
ABLqU1b8lQ8AWun07R8k5tqTmNvCARrAPRUqls/ryER+3Y9YEcxEaTc3jKNZFLbc
|
||||||
|
T6YtcnkdhxsiO136wtiuatpYL91RgCmuSpR8+7jEHhuFU01iaASu7ypFrUzrKHTF
|
||||||
|
bKwiLRQi1cMzVcLErq5CDEKiKhUkoDucyARFszrGt9vNIl/YCcBOkcNvM3c05Hn3
|
||||||
|
M++C29JwS3Hwbubg6WO3wjFjoEhpCwU6qRYUz3MRp4tHO4kxKXx+oQnUiFnR7vW0
|
||||||
|
YkNtGc1RUDHwecCTFpJtPb7Yu/E=
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var ClientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAo3q//YITCuCWOSLPAdXSGUU+KvydADr63dy5CDTW5wBovnh9
|
||||||
|
9Lb0r7iZUkSklk0nMbqVILMGt87MuuW2sdge2paWMlhMlh1R5gWuKSQUahF6pHrD
|
||||||
|
O9fOeUEHxpK0hI1l/gGBKP5DjoGNALu9m2AEkUG02BXZJ0AVUpbtgXDtf6AbdSlt
|
||||||
|
ZrlCiETkMuzuYZ8xHDS/AhnR6d8TMQ0hh1dj2UpR0jrMmMg5Im3+D7r35NuiCSoy
|
||||||
|
LCYSecBLCQ4TZnylLHMLzhgOhReqCdwaKeunliDPascgGvtcabEak7grPzyD2nUV
|
||||||
|
Zt1yysTXnsV87OF+b1tuwXFFo6W3KGqTtwik5QIDAQABAoIBAQChJ5dts7VL6ruM
|
||||||
|
FXlViM/1Y2H2hFHM8VduMHEi2tvimm+nHCamf1jUhLh39fz9wY7aoeDyfCkqNy1x
|
||||||
|
LJQd2zwHJZ1ogcz1ym96vqzCF7QcH6Dz1aTyMDp1I5sjsGlNpgoeDKOjoos8Rw+V
|
||||||
|
4nz2VwAJpWk9/sOzwqOCaBA3ovgs7zdpPcFhMMle0v79TOUBQ/aa86X0xtRDxwuH
|
||||||
|
hT8Z2t5hPjSLAjLO9cT0i5bYVVVnvq1ZTGXETXEwi7mMI2HPLELdcTSwesxTTRpt
|
||||||
|
ACIJOuwHPK5KxxC2HFHTyS0THaCDCR9Hqk8lwKEa+CBmjeCc2MwXuYsVGKsm0FaZ
|
||||||
|
viS+fGBBAoGBANcglIBOb8WA+BR+F7Bi7jU3nVtalU4DAHKbhSYg8EAxMIuWMq14
|
||||||
|
UK+h2Qz2RrT1ezegVAm/NEqLX28vhYm1yz2RHDCUqAughKvhwNJ2mkLLREsJNATw
|
||||||
|
AMXDS2KhDPIsbJMKY66Gci17+q2FhyXiW10dxpTReVqnOiT1qeUilPkRAoGBAMKK
|
||||||
|
HG5EGaiF3brr8swsmPaqq33aXqo1k40/pd97xuauPIC/HBLeu+g6w3z8QecOKEYk
|
||||||
|
+Z5R/o/rsjCIpG4uF19lyAKZ9IgGpHX0rbEfWEyl5WARDOegXHGVfj1DNGhZEtO+
|
||||||
|
kSq1i5LteQSfRXvarbhbV7bKgvJYtLK5960XaM6VAoGBALyIPfzQQN5LL57t/p7D
|
||||||
|
pNWYvtwf37d1o//M0fzfYw4uzceXQySJy9SQN+NHNiJC/NB8PwonupEV4fZUJGjS
|
||||||
|
nKKBOL5OmZNPAtaLy2vnKzwcXeaQ0zj8iQDILZnrYKggTKr0sPVzuD6qZ7+IxS9r
|
||||||
|
V/ycKrujdQIAilF3xoQcMYixAoGAfx2NvENFXMez/brFGMKfZLZafk7dAm0lr+sB
|
||||||
|
8MjJS9xX7mxx5Kajs/gJ2rZePaMTj9oDPX8oTlRdR7dRcikt3oj8Ky78CJIGjojF
|
||||||
|
ofHwWY0hFyes/gDbxuA+77rlGLXzRmbEJlsgC26eX/XOikJ2tvsAkpE7BS4PTKWV
|
||||||
|
gAXG1w0CgYEAq4rhFKVi37qKI8kVHO5O3lvRfKOmiMs2j2X+3T2QSTGcGRZD31EO
|
||||||
|
ImRWsYCAaX97313xYhjTT4jJzNU4fdnJ5hFte+Nt7BK1h/ze4+0XGJBK7wnDqaqg
|
||||||
|
kL0SB6nxr/Gqnhwx+wEaLkfhiy7Gx0E0IoSGEELsW/MMgvzzAo1/jaM=
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var ClientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDVTCCAj2gAwIBAgIJANWw74P5KJk3MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
||||||
|
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
||||||
|
DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjA4MTYwNAYDVQQDFC1nZW5l
|
||||||
|
cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jbGllbnQwggEiMA0G
|
||||||
|
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjer/9ghMK4JY5Is8B1dIZRT4q/J0A
|
||||||
|
Ovrd3LkINNbnAGi+eH30tvSvuJlSRKSWTScxupUgswa3zsy65bax2B7alpYyWEyW
|
||||||
|
HVHmBa4pJBRqEXqkesM71855QQfGkrSEjWX+AYEo/kOOgY0Au72bYASRQbTYFdkn
|
||||||
|
QBVSlu2BcO1/oBt1KW1muUKIROQy7O5hnzEcNL8CGdHp3xMxDSGHV2PZSlHSOsyY
|
||||||
|
yDkibf4Puvfk26IJKjIsJhJ5wEsJDhNmfKUscwvOGA6FF6oJ3Bop66eWIM9qxyAa
|
||||||
|
+1xpsRqTuCs/PIPadRVm3XLKxNeexXzs4X5vW27BcUWjpbcoapO3CKTlAgMBAAGj
|
||||||
|
ZDBiMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMC
|
||||||
|
BggrBgEFBQcDATApBgNVHREEIjAghwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVs
|
||||||
|
dC5zdmMwDQYJKoZIhvcNAQELBQADggEBACDF/OlwaoxLu4h4bvyNJnuQdsw3O2Zz
|
||||||
|
xEADJOkeqM389hYmTlfyPFFhHocFW79ObUxa+73haBXTI6wFP0wSr2jaSQ86j85/
|
||||||
|
V99S8WP/D4jmVqXXTe43o3WvvKFUHfJ7BO4OEHED0orRe11IcSkP8emSHHehqXxg
|
||||||
|
V0P3s1cZao7pPplRSZjcOC5dimEfKnx7ibBh22a8wjq2vPbGxTDf56nkeq4/fbc5
|
||||||
|
MaAAeVpyFlN6ueREaz7ixy0r3yLMhC9xr4E6p8VvWsYBkQHWyukiUzbwVUwpK+Rw
|
||||||
|
Hy80c9+1z7X9/eKr9N/fzwbfrGjb3rbi7o1UHEEwiLaq1a+Df6dP92o=
|
||||||
|
-----END CERTIFICATE-----`)
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package testdata contains generated key pairs used by the unit tests of
|
||||||
|
// mutating and validating webhooks. They are for testing only.
|
||||||
|
package testdata // import "k8s.io/apiserver/pkg/admission/plugin/webhook/testdata"
|
|
@ -54,24 +54,24 @@ DNS.1 = webhook-test.default.svc
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Create a certificate authority
|
# Create a certificate authority
|
||||||
openssl genrsa -out caKey.pem 2048
|
openssl genrsa -out CAKey.pem 2048
|
||||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=${CN_BASE}_ca"
|
openssl req -x509 -new -nodes -key CAKey.pem -days 100000 -out CACert.pem -subj "/CN=${CN_BASE}_ca"
|
||||||
|
|
||||||
# Create a second certificate authority
|
# Create a second certificate authority
|
||||||
openssl genrsa -out badCAKey.pem 2048
|
openssl genrsa -out BadCAKey.pem 2048
|
||||||
openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=${CN_BASE}_ca"
|
openssl req -x509 -new -nodes -key BadCAKey.pem -days 100000 -out BadCACert.pem -subj "/CN=${CN_BASE}_ca"
|
||||||
|
|
||||||
# Create a server certiticate
|
# Create a server certiticate
|
||||||
openssl genrsa -out serverKey.pem 2048
|
openssl genrsa -out ServerKey.pem 2048
|
||||||
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook-test.default.svc" -config server.conf
|
openssl req -new -key ServerKey.pem -out server.csr -subj "/CN=webhook-test.default.svc" -config server.conf
|
||||||
openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf
|
openssl x509 -req -in server.csr -CA CACert.pem -CAkey CAKey.pem -CAcreateserial -out ServerCert.pem -days 100000 -extensions v3_req -extfile server.conf
|
||||||
|
|
||||||
# Create a client certiticate
|
# Create a client certiticate
|
||||||
openssl genrsa -out clientKey.pem 2048
|
openssl genrsa -out ClientKey.pem 2048
|
||||||
openssl req -new -key clientKey.pem -out client.csr -subj "/CN=${CN_BASE}_client" -config client.conf
|
openssl req -new -key ClientKey.pem -out client.csr -subj "/CN=${CN_BASE}_client" -config client.conf
|
||||||
openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf
|
openssl x509 -req -in client.csr -CA CACert.pem -CAkey CAKey.pem -CAcreateserial -out ClientCert.pem -days 100000 -extensions v3_req -extfile client.conf
|
||||||
|
|
||||||
outfile=certs_test.go
|
outfile=certs.go
|
||||||
|
|
||||||
cat > $outfile << EOF
|
cat > $outfile << EOF
|
||||||
/*
|
/*
|
||||||
|
@ -95,8 +95,8 @@ EOF
|
||||||
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
||||||
echo "// and holds raw certificates for the webhook tests." >> $outfile
|
echo "// and holds raw certificates for the webhook tests." >> $outfile
|
||||||
echo "" >> $outfile
|
echo "" >> $outfile
|
||||||
echo "package validating" >> $outfile
|
echo "package testdata" >> $outfile
|
||||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
for file in CAKey CACert BadCAKey BadCACert ServerKey ServerCert ClientKey ClientCert; do
|
||||||
data=$(cat ${file}.pem)
|
data=$(cat ${file}.pem)
|
||||||
echo "" >> $outfile
|
echo "" >> $outfile
|
||||||
echo "var $file = []byte(\`$data\`)" >> $outfile
|
echo "var $file = []byte(\`$data\`)" >> $outfile
|
|
@ -151,16 +151,16 @@ func (a *GenericAdmissionWebhook) SetExternalKubeInformerFactory(f informers.Sha
|
||||||
// ValidateInitialization implements the InitializationValidator interface.
|
// ValidateInitialization implements the InitializationValidator interface.
|
||||||
func (a *GenericAdmissionWebhook) ValidateInitialization() error {
|
func (a *GenericAdmissionWebhook) ValidateInitialization() error {
|
||||||
if a.hookSource == nil {
|
if a.hookSource == nil {
|
||||||
return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a Kubernetes client to be provided")
|
return fmt.Errorf("GenericAdmissionWebhook admission plugin requires a Kubernetes client to be provided")
|
||||||
}
|
}
|
||||||
if err := a.namespaceMatcher.Validate(); err != nil {
|
if err := a.namespaceMatcher.Validate(); err != nil {
|
||||||
return fmt.Errorf("the GenericAdmissionWebhook.namespaceMatcher is not properly setup: %v", err)
|
return fmt.Errorf("GenericAdmissionWebhook.namespaceMatcher is not properly setup: %v", err)
|
||||||
}
|
}
|
||||||
if err := a.clientManager.Validate(); err != nil {
|
if err := a.clientManager.Validate(); err != nil {
|
||||||
return fmt.Errorf("the GenericAdmissionWebhook.clientManager is not properly setup: %v", err)
|
return fmt.Errorf("GenericAdmissionWebhook.clientManager is not properly setup: %v", err)
|
||||||
}
|
}
|
||||||
if err := a.convertor.Validate(); err != nil {
|
if err := a.convertor.Validate(); err != nil {
|
||||||
return fmt.Errorf("the GenericAdmissionWebhook.convertor is not properly setup: %v", err)
|
return fmt.Errorf("GenericAdmissionWebhook.convertor is not properly setup: %v", err)
|
||||||
}
|
}
|
||||||
go a.hookSource.Run(wait.NeverStop)
|
go a.hookSource.Run(wait.NeverStop)
|
||||||
return nil
|
return nil
|
||||||
|
@ -248,7 +248,6 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
|
||||||
if ignoreClientCallFailures {
|
if ignoreClientCallFailures {
|
||||||
glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||||
utilruntime.HandleError(callErr)
|
utilruntime.HandleError(callErr)
|
||||||
// Since we are failing open to begin with, we do not send an error down the channel
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +279,7 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
|
||||||
return errs[0]
|
return errs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: factor into a common place along with the validating webhook version.
|
||||||
func (a *GenericAdmissionWebhook) shouldCallHook(h *v1alpha1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
func (a *GenericAdmissionWebhook) shouldCallHook(h *v1alpha1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||||
var matches bool
|
var matches bool
|
||||||
for _, r := range h.Rules {
|
for _, r := range h.Rules {
|
||||||
|
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/testdata"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
@ -96,7 +97,7 @@ func ccfgSVC(urlPath string) registrationv1alpha1.WebhookClientConfig {
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Path: &urlPath,
|
Path: &urlPath,
|
||||||
},
|
},
|
||||||
CABundle: caCert,
|
CABundle: testdata.CACert,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ func (c urlConfigGenerator) ccfgURL(urlPath string) registrationv1alpha1.Webhook
|
||||||
urlString := u2.String()
|
urlString := u2.String()
|
||||||
return registrationv1alpha1.WebhookClientConfig{
|
return registrationv1alpha1.WebhookClientConfig{
|
||||||
URL: &urlString,
|
URL: &urlString,
|
||||||
CABundle: caCert,
|
CABundle: testdata.CACert,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,12 +579,12 @@ func TestAdmitCachedClient(t *testing.T) {
|
||||||
|
|
||||||
func newTestServer(t *testing.T) *httptest.Server {
|
func newTestServer(t *testing.T) *httptest.Server {
|
||||||
// Create the test webhook server
|
// Create the test webhook server
|
||||||
sCert, err := tls.X509KeyPair(serverCert, serverKey)
|
sCert, err := tls.X509KeyPair(testdata.ServerCert, testdata.ServerKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
rootCAs := x509.NewCertPool()
|
rootCAs := x509.NewCertPool()
|
||||||
rootCAs.AppendCertsFromPEM(caCert)
|
rootCAs.AppendCertsFromPEM(testdata.CACert)
|
||||||
testServer := httptest.NewUnstartedServer(http.HandlerFunc(webhookHandler))
|
testServer := httptest.NewUnstartedServer(http.HandlerFunc(webhookHandler))
|
||||||
testServer.TLS = &tls.Config{
|
testServer.TLS = &tls.Config{
|
||||||
Certificates: []tls.Certificate{sCert},
|
Certificates: []tls.Certificate{sCert},
|
||||||
|
@ -642,9 +643,9 @@ func newFakeAuthenticationInfoResolver(count *int32) *fakeAuthenticationInfoReso
|
||||||
return &fakeAuthenticationInfoResolver{
|
return &fakeAuthenticationInfoResolver{
|
||||||
restConfig: &rest.Config{
|
restConfig: &rest.Config{
|
||||||
TLSClientConfig: rest.TLSClientConfig{
|
TLSClientConfig: rest.TLSClientConfig{
|
||||||
CAData: caCert,
|
CAData: testdata.CACert,
|
||||||
CertData: clientCert,
|
CertData: testdata.ClientCert,
|
||||||
KeyData: clientKey,
|
KeyData: testdata.ClientKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cachedCount: count,
|
cachedCount: count,
|
||||||
|
|
|
@ -1,216 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This file was generated using openssl by the gencerts.sh script
|
|
||||||
// and holds raw certificates for the webhook tests.
|
|
||||||
|
|
||||||
package validating
|
|
||||||
|
|
||||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEAt8E1XykA4860Tj7mypnsSU+hW0taUEvz26a5rgFSrwgKe1g+
|
|
||||||
zOXc0XoAdnWivWKwWXTW+P1mmjMApEf8ndfPy+juKIrPKP6ccF31iPvOGfNRm/g/
|
|
||||||
ulZAJnAjBn0zkZ9ARhpdDxKwDpwIKSrTna5GB/gX0VbHQ/M23u0RjVUZuNM9cayW
|
|
||||||
HReRx7xlOQD+uREQ/wh1zkgQime+rji5U3jxB9YD3zfTeGBJdrq9ptTdkPEIQUKf
|
|
||||||
DM8SnM8fleEPkBq3XrhfmAEfkHBGpn4Hc82tk/oEZ+LMyMaR/GmzdJXU2/d4zixC
|
|
||||||
Dgqdg1nB76uXJ4aITX2BuR+ttmAI0b6Si4UvHwIDAQABAoIBAA/Msg0blnL/++ra
|
|
||||||
Z7e14mYvTZ1u7jYHQdF6FW8LuBNKqrQOU2AEx6bPSajl1ndYO/eFH1LLXv4VMpHt
|
|
||||||
ip/7xWcwAQJFZSiOM99JhOohVIhQroytnLUl42AqtihBraRwv/MHI0c/gRnQercn
|
|
||||||
coiVSno2771VK88A44/pbF/tmEeW5Nq7bwHrtjdt3MDKjv2LQaPToDzBivSwz/F8
|
|
||||||
3dMBpCUKT3tKC6QiDAFi4WaVOqXZrDfm/HJ1L8LWYjrcGTwwzMGpDfQEMxhq+2AR
|
|
||||||
Ya89jKF1I2+3kgXrZER7eHktUEQ0bGckSmAN9yo01rdm7E5gmmPuTK13riSFWrJn
|
|
||||||
/Dg21PECgYEA3vM0OLGevaUP4geBv1nPLpANC551Wf8wu2QG+Ts1/LFQHtFTOTst
|
|
||||||
JjFy/XT90ki1wni8P/pcIbMEXDJJezR4giWfiwMzr/E4arkJc5rJwV3kcmGrVihS
|
|
||||||
9BIJVlWq8kPmklTctfoqjDMa7tkYZoYStg+1Xljvw/HJFqZ6VoWyxFcCgYEA0v6S
|
|
||||||
Fx960kQyqPYyQMaZpce9rAsgGBJ4uMU6dXVfYxDy0CEKZ1lV1xwUg9eWNFj7E46A
|
|
||||||
RJDl9fR2KztTbgBobEOCVlO9QftY8RiIzibq8R4P5XyEV+TCkPk+eYffDZfOueGK
|
|
||||||
uCzBcAcl12SkAy/KMeS0+/+KYfetGyh23GH/bnkCgYEAnqMefVilQvu4GXSN9cHJ
|
|
||||||
kbAeGC5gAfF6k1vROnXPLEZeZA890HMy5QI6d+5OzNm/uuh9ymgyNihS6ec+MdRc
|
|
||||||
Cv8KTrewh3h0VDvlZcS12kkcy+aDK4L1w4Ux76R1RnzaCzUm9rVSoP+cImeG3Sx5
|
|
||||||
E+KJguB1ek8Ibn12fyoS0XECgYEAvKyAHsU7o0Lwuj5NebceNiyC45GfRWdfJHrZ
|
|
||||||
Z6dpgMDrIEorb3dnV0/42Fy0KGNZQYewE6Auwt2zvbzzQe6Dcix8JI4FMzd7tTxn
|
|
||||||
OVF7zdlABcpu3dnmUpVO1IY3Y4RYi8evsDn1UCRUJmQMdf0KJcuKO72rFSfRV/O7
|
|
||||||
Nh87tqECgYAN21eEb68fg7z7WqnUhL24SilIFNx7qmyhFZ5ZbIuNBrCOW4lxj7z4
|
|
||||||
1A4WyB3Wj2CeTeFrFamq+bgI5gl4DNzcml8bFxij3WDbwzq6C2FTJ1U+Ax/Z9Y60
|
|
||||||
qdc1fMRy+0Wkglk26VZ+xA4gW0eetIDXf/IocFYZ56ti36lWfXMKFA==
|
|
||||||
-----END RSA PRIVATE KEY-----`)
|
|
||||||
|
|
||||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDPTCCAiWgAwIBAgIJALl4JUWeGrsQMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
|
||||||
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
|
||||||
DTE3MTAxNzAxMDcyM1oYDzIyOTEwODAyMDEwNzIzWjA0MTIwMAYDVQQDDClnZW5l
|
|
||||||
cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI
|
|
||||||
hvcNAQEBBQADggEPADCCAQoCggEBALfBNV8pAOPOtE4+5sqZ7ElPoVtLWlBL89um
|
|
||||||
ua4BUq8ICntYPszl3NF6AHZ1or1isFl01vj9ZpozAKRH/J3Xz8vo7iiKzyj+nHBd
|
|
||||||
9Yj7zhnzUZv4P7pWQCZwIwZ9M5GfQEYaXQ8SsA6cCCkq052uRgf4F9FWx0PzNt7t
|
|
||||||
EY1VGbjTPXGslh0Xkce8ZTkA/rkREP8Idc5IEIpnvq44uVN48QfWA98303hgSXa6
|
|
||||||
vabU3ZDxCEFCnwzPEpzPH5XhD5Aat164X5gBH5BwRqZ+B3PNrZP6BGfizMjGkfxp
|
|
||||||
s3SV1Nv3eM4sQg4KnYNZwe+rlyeGiE19gbkfrbZgCNG+kouFLx8CAwEAAaNQME4w
|
|
||||||
HQYDVR0OBBYEFJ+UXeXeN9DfxuCA65LuhRaXI5bnMB8GA1UdIwQYMBaAFJ+UXeXe
|
|
||||||
N9DfxuCA65LuhRaXI5bnMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
|
|
||||||
AJlfuK7LcFxXCjB6rnRYvjIFF3JewaPGx273YDpV2DkvCJXvez+mDC6ZfD1U7nEw
|
|
||||||
P+0A+NJctU2Vv+bbhh1vXlmBUoA9zKyIPld0pXDt8PxK8L9QRdLdzN96MtUjE0Cr
|
|
||||||
jBDxy3eIke+ElQQyU3MSx1uohZao40WOTDvR1fBswrFGhFNtELgwT3zIJ7tMO4ws
|
|
||||||
1f6LRVB4xsD6cPNUmFyJW6UqecJ3ZSeErF0r8uN4Ta0zUoJ03CflwsgujoVNpIPR
|
|
||||||
/VVElRE6Cd2C1i3qLBMOZ+aQrxGw4teNXilXbKtwJzpDYU+bypPgim+vxAwD9XnQ
|
|
||||||
J/rCqIaSYOI/3WkZmQ+E6aE=
|
|
||||||
-----END CERTIFICATE-----`)
|
|
||||||
|
|
||||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpgIBAAKCAQEAsLjPsYMRv5T3+OoPbE+mKqUkBrn6ZQZU28UulLaS8UsaX/Dx
|
|
||||||
o/N2P6YGSlBeEnO0IV7fHqfdlgTRqqhlhX9L+suj963skZUmwoHB54lsHBYnXEBr
|
|
||||||
UlkVn5Wv+BEp5FX9iC9EV+wyWxuddGqElNUSMvuC71lRAE1edKbFZYIgqpGson6u
|
|
||||||
kcn5cpBoyFjFyxKhuKh5WTCQGYFZBtIhcggyduUw5+JzbPUlrlOcCiW8aQwL0f+s
|
|
||||||
9vA+4lDcngyOXDgAOip/Cr3lYiajhv41TyMNR9aSCH3gwI06QabQpXeHmYKjdMDO
|
|
||||||
epo9czAOJ2ZI+o2sKRZGUvyfACg6VJpU2jiWdwIDAQABAoIBAQCClDxbFNcDcaZ8
|
|
||||||
1S4KQRwt/JIPKlJ7XV9MeHl/xxvykSTu6VETbOzeAOY6+QFZrwbVdY110GGp3Ouz
|
|
||||||
pvRE3ReeO+RvOaNIuyXFqS1G0UMBydjRkIP8d/jDT06UBNKodmV8wDhGoy9eJJyG
|
|
||||||
jcJjWsE0zKUmCCATEhgOJ8BJzgonHXwSkgjhBophYpDNGi4W1Ke8a80V0S26UOsV
|
|
||||||
b72mBqPYDAcGNR9dFpJGYRRdZ77SjcAAOFOqnU6KXUFHvitNYSkUG22wHa4+yuO6
|
|
||||||
fTDUitdXhxCcFCWEFyX6Chm3lmf1NgSI4bni46K6/BC9mlNjZ+7QrTJTTp1/WyHj
|
|
||||||
fV2TnwRhAoGBAN2kTatss76FZwKFamPSvX45ulo0/U9LB1Eb9A7/M+enfAJGPPuk
|
|
||||||
LRdy3JjDiEKxgsBlA7zy0Gc9wUjBGmPBOiJmCTfmMykoqV1qVH5YQcuVUaKE589a
|
|
||||||
iUAUn6PNcNsnu2Z8BIDU3Fvyq2KJammSz/zqNtkH7I6SDur6Sb9SRJORAoGBAMwd
|
|
||||||
5OkOOGvZKxnJz8s5hwDicN2Fk5zxWpA74mgUt44sOiG3F8/7y/ntT/O3DGKz/yDb
|
|
||||||
2Ju6Yf3ojqYnirAPlWLxIm0Y3K9gNQffJ5cpe/kCzbhCLAnLk6YXu8ZVoMMq/0LF
|
|
||||||
f0bh+UAktqlFi5Tl+0LGAGd9wWxAe7/DHmolBfWHAoGBAJxLy/Wx3wLgQebWPFMO
|
|
||||||
flAv10jbizHKX+uDgdS9hFW8lsdnzoNJn/6kIgmcAU++q8yOr1ckB3B2bQGoIrrr
|
|
||||||
vNobCC8iJzvED8LvQ4whIqy0rG+lt25SkuzcXkL9kbMJzq4TkH1lHcu9UbxX2PF/
|
|
||||||
9SmN5IWhf+B+AQUU4MKI+hDxAoGBAIRXrZ/d9H8Yo3VpAC2H8xyDtSIsBXVwl4OF
|
|
||||||
EFrjc8/epSJPEEVtwOcfEwO133Xvtq+bW2o9AmQacMMSSD23HOi159hMkmmzOy8L
|
|
||||||
ZSQBZbwiMTgSz3LaZ7T9FmaWBlIEgtTMMKXIxk7sfvJpgQLdyneU4ZY4VzzU4meH
|
|
||||||
HyU7NA3pAoGBAM53r4pKMsdH9c4w/aRiRZeRge+MdPqdBo4Kje50uXVUaplJr1fJ
|
|
||||||
9Bm2P1oSfr+Zh6pNyDQE2OhptOxskd/+XDC4i6+MZ1iR1JHFq4oTF5OUYc3Je23G
|
|
||||||
FjD/vUn+ha2d750IPhsDztb8XyGfQdn7oo0ikhg1Ayjix6LE0GAd+Ve2
|
|
||||||
-----END RSA PRIVATE KEY-----`)
|
|
||||||
|
|
||||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDPTCCAiWgAwIBAgIJANumDUaVJHIhMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
|
||||||
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
|
||||||
DTE3MTAxNzAxMDcyNFoYDzIyOTEwODAyMDEwNzI0WjA0MTIwMAYDVQQDDClnZW5l
|
|
||||||
cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI
|
|
||||||
hvcNAQEBBQADggEPADCCAQoCggEBALC4z7GDEb+U9/jqD2xPpiqlJAa5+mUGVNvF
|
|
||||||
LpS2kvFLGl/w8aPzdj+mBkpQXhJztCFe3x6n3ZYE0aqoZYV/S/rLo/et7JGVJsKB
|
|
||||||
weeJbBwWJ1xAa1JZFZ+Vr/gRKeRV/YgvRFfsMlsbnXRqhJTVEjL7gu9ZUQBNXnSm
|
|
||||||
xWWCIKqRrKJ+rpHJ+XKQaMhYxcsSobioeVkwkBmBWQbSIXIIMnblMOfic2z1Ja5T
|
|
||||||
nAolvGkMC9H/rPbwPuJQ3J4Mjlw4ADoqfwq95WImo4b+NU8jDUfWkgh94MCNOkGm
|
|
||||||
0KV3h5mCo3TAznqaPXMwDidmSPqNrCkWRlL8nwAoOlSaVNo4lncCAwEAAaNQME4w
|
|
||||||
HQYDVR0OBBYEFAYhiaN5L1bHf3VQO9bAwLTCFOyZMB8GA1UdIwQYMBaAFAYhiaN5
|
|
||||||
L1bHf3VQO9bAwLTCFOyZMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
|
|
||||||
AAb6qDKiWDuYbFI3f/AGkHaiWnVrdU/2oQ1P03N/CD0DEGzRTmEQrl5l0pHDUJ5g
|
|
||||||
XTszW/5Bgjgzx8HLG3VeMQlZpCeGrUBsWWIlGFsdfAKQ8xkB1JYNCtb920WBCOpZ
|
|
||||||
FewLzQwbRyeYju+VP7lq+IF3htOTbeXRax61c0qu2o402NbKCNMlwAWMWl92dji5
|
|
||||||
zJG0U0g+U+GC1QVyRlpf6hsXONgNOWTuZJgZDmN0exZOH7rF9syr3lfDwX0I/no7
|
|
||||||
zIJHt7Tx9oanqZbj/Oe8FC4jYNJiZryQ4MaWBk7Op+aR5bUgk5SwA9pLuKLm3Zyl
|
|
||||||
KkC1zS2x3RD1WxxiJjXcJ3M=
|
|
||||||
-----END CERTIFICATE-----`)
|
|
||||||
|
|
||||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEA4Slves29h8gstITXD8cuHT0sZzP9x7Ip5pcSrQgjIpk9V7zB
|
|
||||||
ulRsFfTaPVMAzq5nlX5qbM6OezidzdC0oVQi7DCRd+TqQi2zNDJNeWAjDMkRa6ir
|
|
||||||
vq9XNuQJlMxi/YdFzjWl8LPMyq+WoW+0gtM86jPOEk69iw0nFqmUTt/4WXLOIDH9
|
|
||||||
ffIiZ6h+x6IJGFqcn4heNE9SK++bMmjijmhIF3i0dKquApVm0+E/NYDDHLzeQ42r
|
|
||||||
BhgPWmuLqGBvVavTHweXRJ9qdZ5LVzMJj1KNNLA4T/vsk17jADJGPikrvZWZ0MJE
|
|
||||||
SPKpDAJ9uxDpIGNg8EBdUoStfrQMMsDEnNE1/QIDAQABAoIBAQC/ROumbk+qoKkZ
|
|
||||||
UB9BD/pkbCrkII5crURa1crPojH2miY5+ea32i9XF4Csx23QJOdpXtIZS/5NPnMO
|
|
||||||
+1P2F/rymO954cP+I8QveuvFR51+pu9dfRMoENsNjfl1pYoRxG/QFFK6foJhS3ex
|
|
||||||
+6pj1/3PFeLgOnNZQ/sIjVWnCyt1DzFUaELE1jsDWtsQ9j2ed1ceqjrEVG0BbB8O
|
|
||||||
LeXUp0lDWgRmF9LexFVd361ew6DM4Zr3l9W3Dubd/6gIPNSRCB7T1usErFKPi8Dq
|
|
||||||
ML2lnO98+gKZHp6DKUISWEwt0By3QRLV09fBTes8fr+gylZfhwX2Q2EjiZ/v0Nlb
|
|
||||||
YGLAZwMhAoGBAPigApUlA8L0AGgVoSuBRthdFVzqLKTIKYimiZirRnXljTs3HvS4
|
|
||||||
Z/RCkXRVP6aQ8tORVUyct0drBUgddu+YPS/hfk8ipYraGonAKWfxL2+sFcxHg9iy
|
|
||||||
O6OxR7jOlI7bG8Ue78lJjqfzpXyi2/ikL2udHLzMeCh3czzix4wKXl2LAoGBAOfX
|
|
||||||
QKCudMGOHb2LCkC1jP2VLUAg09Y7q7sX5avNXhZsLNhVgu1i8FNTTXXCHwyBLvxY
|
|
||||||
AX7AnYaUP5K1YOyHVXnfqp2uuctR8tm7TUh9mw3RGnE+D9yw39RjlF4ZjZMtBC0+
|
|
||||||
5/A2upRqY5H2pepxi/bPCAC58UgHbvjTS3LD6zuXAoGAQUj7BKDgmPurc6liVeMv
|
|
||||||
cDcZGfnf2TE6PsjETtOCwAiUCl2SAl695VTpjuunuBxNtyJtjJ2GPvmqPGKITafj
|
|
||||||
QURr/2mwoIJe/5b3CHU7qI4+dxK8W1WJ9ZTiqXONbOm6JAvYmTl4fT+sT8sQCf1K
|
|
||||||
+m4aErV6Q94B45YFIg/C8bsCgYEAx2pkAZHthasrM6UL3ZsLufb9pCJYc/aBgX1N
|
|
||||||
pRgRrPHBJRdwdaXbl6CYiQi/Ui8v7gf4yUD+fgq4IAX5Z5oE0L6tb9Ihp5xGajfs
|
|
||||||
gsTfgOPyfaNnW2mcLYC11rbeCtD2vcBVGk7I7+4O1Tc1gVHHlTSA6rcFrfIO5uJA
|
|
||||||
DGguxuMCgYB9y4JY44YLVpxbHOmQcp5XzN2uDESkBDmzXhn6Kx4wyyMI1/J2a44p
|
|
||||||
68AL0TaVWK1vvzV8X4f+92ufvUCXuuItIVDvkSdKMl6kL54djCA25tGuxNHxMk7e
|
|
||||||
/l4fshoaRwF3ybwHbREMOy8pQHrsek7m21sC/q/DIDN/IPdqo7aaVw==
|
|
||||||
-----END RSA PRIVATE KEY-----`)
|
|
||||||
|
|
||||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDQDCCAiigAwIBAgIJAM2Hyhl1N+5sMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
|
||||||
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
|
||||||
DTE3MTAxNzAxMDcyNFoYDzIyOTEwODAyMDEwNzI0WjAjMSEwHwYDVQQDDBh3ZWJo
|
|
||||||
b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
|
||||||
AoIBAQDhKW96zb2HyCy0hNcPxy4dPSxnM/3HsinmlxKtCCMimT1XvMG6VGwV9No9
|
|
||||||
UwDOrmeVfmpszo57OJ3N0LShVCLsMJF35OpCLbM0Mk15YCMMyRFrqKu+r1c25AmU
|
|
||||||
zGL9h0XONaXws8zKr5ahb7SC0zzqM84STr2LDScWqZRO3/hZcs4gMf198iJnqH7H
|
|
||||||
ogkYWpyfiF40T1Ir75syaOKOaEgXeLR0qq4ClWbT4T81gMMcvN5DjasGGA9aa4uo
|
|
||||||
YG9Vq9MfB5dEn2p1nktXMwmPUo00sDhP++yTXuMAMkY+KSu9lZnQwkRI8qkMAn27
|
|
||||||
EOkgY2DwQF1ShK1+tAwywMSc0TX9AgMBAAGjZDBiMAkGA1UdEwQCMAAwCwYDVR0P
|
|
||||||
BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATApBgNVHREEIjAg
|
|
||||||
hwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVsdC5zdmMwDQYJKoZIhvcNAQELBQAD
|
|
||||||
ggEBAFwJ5UtHJsSx7mrJ7X51XDVg8ocNdWYLuehWfpM2Tlrv9kklONF7VehS+9kg
|
|
||||||
MjSiuXtIhtEEN7GIy08sl6rhANtwXxWhj5b+qPNSNiHGNRvmHkCJuO2PGG7TpSpH
|
|
||||||
CfOgX+HH9CnX/piC7Uqr+vmS+SmhSjIyw1bUtP9cDmFvNQB9/0qvcXZP/oX90jsa
|
|
||||||
qF6fQvKP/OtRcW/kyWmhzqeIMufru82Hbrf/WJuQXCvpgtY43cOlHVEr8X2PbF9F
|
|
||||||
t4eliujfewSu1cyXNcT5KcriCvZyXU/d8UHm+z9rnMJdC4bfvwOLeG7VmEG1Vp/X
|
|
||||||
sRiMsjRcun8Jvbl7BbH86nu1xus=
|
|
||||||
-----END CERTIFICATE-----`)
|
|
||||||
|
|
||||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEowIBAAKCAQEA05j8mb6tyOYOxfdmra9nlBu+VefZ46d+Q018EEi8LLpXFovP
|
|
||||||
+4D6mdZiG2K1Or9Kx7wpiG4gyrqn8aENTEbREtw7+GWpsrtWcOnmtCoMrcqV97S+
|
|
||||||
TurL5DY1Rh9XOBxa/QninOnP4O1dASYmi0uZBZW/Neidwt70/sb3VGxj8Ex8rzD/
|
|
||||||
LWsiVj+ijkK2A18+S2m01HNuNramJiJ8Ns5VxHj5TspRIBik1NEfEDFEi42ddZ0n
|
|
||||||
9q4u3eZmFC1QqMLB/371+oFrieC+gmbg2FCMUw4LIiUMWURhUP8kXNEFCDWvTLbM
|
|
||||||
40wU6lejJw+JF0SBs3AIcI4GJeSfxEuT2AFCBwIDAQABAoIBAC3Rb8ke1+SrpEFL
|
|
||||||
vAkZ9TTF+SYC6VR5XUbXjWi9RznsM5VnOub728fZ+y5w5ktNRrUPUnL/XcxoNJuG
|
|
||||||
wylkIDuUQswbv247UJFspI8Yl9w+BNE5awgNoY7OCiUf/jPhN/aY4GAX5PKQk1X/
|
|
||||||
W9NH0F+8OEZFE3wx6R0OGlpGijFrCbEoqfMcReOVkXt9jjHAxHxI6erMlQvQ6Jop
|
|
||||||
KZ+OndRt8ilNtcjZLxAK8d3odgXN7OGezi74/VnG8b6NVmXsZMRkgS5xcP/42h2e
|
|
||||||
GDGGm4Gia8x0lcosgr+LdZ4FMEITj2p84PGNoeoh7PtTMUQs2qnfPPbpQpab6w9/
|
|
||||||
7j7jRXECgYEA7/R8ByzNFUfcBOW0F8mmQ4bAmJcjuVEBay/XFf8gxWrefxsIEBu/
|
|
||||||
n3GPI10bxGdEpSQGln1P9VCyZPauHPQC6DNB2OQjbmAujuigj2uM3GbXmYt20z2s
|
|
||||||
iUHhZQstznEO+BLqOMhk2SICnwsecPn0jP2MvWS7tY+szwvdLROHZj8CgYEA4b8V
|
|
||||||
iGU8/3mXGYRsT/tuuWjnalstCYzKosK92K8PS6LnAjv8t/8CMrL5gHAGXdf6fEAh
|
|
||||||
qDndlB1VkQ/ymiqR35el/ErVRt1/2pbwTLSQzAGJawY/osnClMnShO8gdQnsJnmi
|
|
||||||
zx909lWVxrKkN38szLQfK2bq4C8z6Bw5+6IxAjkCgYBnEUas9kto1qLk352Jki3+
|
|
||||||
V0UmxdSsZuULG1NxuVJkOdE0G3JNKP4YCHkJIZcpt4m+vUivH0hXAMB/qY2EFjOh
|
|
||||||
dVLVTLkDUgDtlXJR6Epq6Sm2ZDc36QfRNSERe8nDIMDjQYylsz3OHlOt6OK8eEDY
|
|
||||||
xpfLShdulzYNAPWRxQ+llQKBgQCmWEvhqdf82PgCkZXOihPZA/giYvUY6GoY7S8/
|
|
||||||
kB/ROETJXLKoUnyoJ0G65tGKLTAiho9Giv0/uy3mKr4149CB1hk1g18NTQJ9bGO9
|
|
||||||
4gAgk7FS79PMfKepQ96gniRomdstrsvNm/xv2Dj5pYFkc43reX7OWJQShjXVf5cq
|
|
||||||
WSWL4QKBgGVoe8yldF7dijRgB0NYJ7LV+xTQxLpSzK+3b5LyrvSTMwGut0O6QsbK
|
|
||||||
S060B37PdwBxMD05yTy8Jcr0abl9+tiLGAdok5Ufnm2e7sNsxf8fLhsyPuReutB8
|
|
||||||
zg1721jMeo9rwnduEi4/U1PIjqYUBIH/jjc9RHSvhk+MVUKXS95R
|
|
||||||
-----END RSA PRIVATE KEY-----`)
|
|
||||||
|
|
||||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDVTCCAj2gAwIBAgIJAM2Hyhl1N+5tMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
|
|
||||||
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
|
|
||||||
DTE3MTAxNzAxMDcyNFoYDzIyOTEwODAyMDEwNzI0WjA4MTYwNAYDVQQDDC1nZW5l
|
|
||||||
cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jbGllbnQwggEiMA0G
|
|
||||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTmPyZvq3I5g7F92atr2eUG75V59nj
|
|
||||||
p35DTXwQSLwsulcWi8/7gPqZ1mIbYrU6v0rHvCmIbiDKuqfxoQ1MRtES3Dv4Zamy
|
|
||||||
u1Zw6ea0KgytypX3tL5O6svkNjVGH1c4HFr9CeKc6c/g7V0BJiaLS5kFlb816J3C
|
|
||||||
3vT+xvdUbGPwTHyvMP8tayJWP6KOQrYDXz5LabTUc242tqYmInw2zlXEePlOylEg
|
|
||||||
GKTU0R8QMUSLjZ11nSf2ri7d5mYULVCowsH/fvX6gWuJ4L6CZuDYUIxTDgsiJQxZ
|
|
||||||
RGFQ/yRc0QUINa9MtszjTBTqV6MnD4kXRIGzcAhwjgYl5J/ES5PYAUIHAgMBAAGj
|
|
||||||
ZDBiMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMC
|
|
||||||
BggrBgEFBQcDATApBgNVHREEIjAghwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVs
|
|
||||||
dC5zdmMwDQYJKoZIhvcNAQELBQADggEBAAgu8K/+UA6V7+AiOPP0Hs3jGTsVjnPB
|
|
||||||
3XRCSWof5LL93iSRu1rI5LYmjS4N80lV0JkaJNvsoAKxETS3MW4rgv6t3kFOyLMw
|
|
||||||
mTfIli3iSBMz4WF55px1yhgF85wghEv2+YRF9aSUqAyz4DmlTGlFCEUx+ntkysUD
|
|
||||||
F97k/jB56EJVqMpSoY5O81vxr21Jpzlryd/UoMVwhYuO3tN0FP+PjoRiQhCGdQTz
|
|
||||||
2H+TQytZ6Xx6B8BE/joh3WBnQ4705jFhFaDSP8DSH45r48dzbxNLJVNeqLQQ0PhI
|
|
||||||
clrHwa1WiAnv+4Ydc5CiXGjLjU0sIvETjVtQGPv/gAykeVJo/4nXM8c=
|
|
||||||
-----END CERTIFICATE-----`)
|
|
|
@ -14,5 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package validating checks a non-mutating webhook for configured operation admission
|
// Package validating makes calls to validating (i.e., non-mutating) webhooks
|
||||||
|
// during the admission process.
|
||||||
package validating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
|
package validating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
|
||||||
|
|
|
@ -28,6 +28,18 @@ type Convertor struct {
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert converts the in object to the out object and returns an error if the
|
||||||
|
// conversion fails.
|
||||||
|
func (c Convertor) Convert(in runtime.Object, out runtime.Object) error {
|
||||||
|
// For custom resources, because ConvertToGVK reuses the passed in object as
|
||||||
|
// the output. c.Scheme.Convert resets the objects to empty if in == out, so
|
||||||
|
// we skip the conversion if that's the case.
|
||||||
|
if in == out {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.Scheme.Convert(in, out, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// ConvertToGVK converts object to the desired gvk.
|
// ConvertToGVK converts object to the desired gvk.
|
||||||
func (c Convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) {
|
func (c Convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) {
|
||||||
// Unlike other resources, custom resources do not have internal version, so
|
// Unlike other resources, custom resources do not have internal version, so
|
||||||
|
|
|
@ -130,3 +130,90 @@ func TestConvertToGVK(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvert(t *testing.T) {
|
||||||
|
scheme := initiateScheme()
|
||||||
|
c := Convertor{Scheme: scheme}
|
||||||
|
sampleCRD := unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "mygroup.k8s.io/v1",
|
||||||
|
"kind": "Flunder",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"Key": "Value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
table := map[string]struct {
|
||||||
|
in runtime.Object
|
||||||
|
out runtime.Object
|
||||||
|
expectedObj runtime.Object
|
||||||
|
}{
|
||||||
|
"convert example/v1#Pod to example#Pod": {
|
||||||
|
in: &examplev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "pod1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: examplev1.PodSpec{
|
||||||
|
RestartPolicy: examplev1.RestartPolicy("never"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: &example.Pod{},
|
||||||
|
expectedObj: &example.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "pod1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: example.PodSpec{
|
||||||
|
RestartPolicy: example.RestartPolicy("never"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"convert example2/v1#replicaset to example#replicaset": {
|
||||||
|
in: &example2v1.ReplicaSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "rs1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: example2v1.ReplicaSetSpec{
|
||||||
|
Replicas: func() *int32 { var i int32; i = 1; return &i }(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: &example.ReplicaSet{},
|
||||||
|
expectedObj: &example.ReplicaSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "rs1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: example.ReplicaSetSpec{
|
||||||
|
Replicas: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"no conversion if the object is the same": {
|
||||||
|
in: &sampleCRD,
|
||||||
|
out: &sampleCRD,
|
||||||
|
expectedObj: &sampleCRD,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, test := range table {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
err := c.Convert(test.in, test.out)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(test.out, test.expectedObj) {
|
||||||
|
t.Errorf("\nexpected:\n%#v\ngot:\n %#v\n", test.expectedObj, test.out)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/admission/initializer"
|
"k8s.io/apiserver/pkg/admission/initializer"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/initialization"
|
"k8s.io/apiserver/pkg/admission/plugin/initialization"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
|
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
|
||||||
|
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
|
||||||
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
|
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
|
||||||
"k8s.io/apiserver/pkg/server"
|
"k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
|
@ -56,8 +57,8 @@ func NewAdmissionOptions() *AdmissionOptions {
|
||||||
options := &AdmissionOptions{
|
options := &AdmissionOptions{
|
||||||
Plugins: &admission.Plugins{},
|
Plugins: &admission.Plugins{},
|
||||||
PluginNames: []string{},
|
PluginNames: []string{},
|
||||||
RecommendedPluginOrder: []string{lifecycle.PluginName, initialization.PluginName, validatingwebhook.PluginName},
|
RecommendedPluginOrder: []string{mutatingwebhook.PluginName, lifecycle.PluginName, initialization.PluginName, validatingwebhook.PluginName},
|
||||||
DefaultOffPlugins: []string{initialization.PluginName, validatingwebhook.PluginName},
|
DefaultOffPlugins: []string{mutatingwebhook.PluginName, initialization.PluginName, validatingwebhook.PluginName},
|
||||||
}
|
}
|
||||||
server.RegisterAllAdmissionPlugins(options.Plugins)
|
server.RegisterAllAdmissionPlugins(options.Plugins)
|
||||||
return options
|
return options
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/initialization"
|
"k8s.io/apiserver/pkg/admission/plugin/initialization"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
|
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
|
||||||
|
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
|
||||||
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
|
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,4 +30,5 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
|
||||||
lifecycle.Register(plugins)
|
lifecycle.Register(plugins)
|
||||||
initialization.Register(plugins)
|
initialization.Register(plugins)
|
||||||
validatingwebhook.Register(plugins)
|
validatingwebhook.Register(plugins)
|
||||||
|
mutatingwebhook.Register(plugins)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package apimachinery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -45,26 +46,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
secretName = "sample-webhook-secret"
|
secretName = "sample-webhook-secret"
|
||||||
deploymentName = "sample-webhook-deployment"
|
deploymentName = "sample-webhook-deployment"
|
||||||
serviceName = "e2e-test-webhook"
|
serviceName = "e2e-test-webhook"
|
||||||
roleBindingName = "webhook-auth-reader"
|
roleBindingName = "webhook-auth-reader"
|
||||||
webhookConfigName = "e2e-test-webhook-config"
|
webhookConfigName = "e2e-test-webhook-config"
|
||||||
skipNamespaceLabelKey = "skip-webhook-admission"
|
mutatingWebhookConfigName = "e2e-test-mutating-webhook-config"
|
||||||
skipNamespaceLabelValue = "yes"
|
skipNamespaceLabelKey = "skip-webhook-admission"
|
||||||
skippedNamespaceName = "exempted-namesapce"
|
skipNamespaceLabelValue = "yes"
|
||||||
disallowedPodName = "disallowed-pod"
|
skippedNamespaceName = "exempted-namesapce"
|
||||||
disallowedConfigMapName = "disallowed-configmap"
|
disallowedPodName = "disallowed-pod"
|
||||||
allowedConfigMapName = "allowed-configmap"
|
disallowedConfigMapName = "disallowed-configmap"
|
||||||
crdName = "e2e-test-webhook-crd"
|
allowedConfigMapName = "allowed-configmap"
|
||||||
crdKind = "E2e-test-webhook-crd"
|
crdName = "e2e-test-webhook-crd"
|
||||||
crdWebhookConfigName = "e2e-test-webhook-config-crd"
|
crdKind = "E2e-test-webhook-crd"
|
||||||
crdAPIGroup = "webhook-crd-test.k8s.io"
|
crdWebhookConfigName = "e2e-test-webhook-config-crd"
|
||||||
crdAPIVersion = "v1"
|
crdMutatingWebhookConfigName = "e2e-test-mutating-webhook-config-crd"
|
||||||
webhookFailClosedConfigName = "e2e-test-webhook-fail-closed"
|
crdAPIGroup = "webhook-crd-test.k8s.io"
|
||||||
failNamespaceLabelKey = "fail-closed-webhook"
|
crdAPIVersion = "v1"
|
||||||
failNamespaceLabelValue = "yes"
|
webhookFailClosedConfigName = "e2e-test-webhook-fail-closed"
|
||||||
failNamespaceName = "fail-closed-namesapce"
|
failNamespaceLabelKey = "fail-closed-webhook"
|
||||||
|
failNamespaceLabelValue = "yes"
|
||||||
|
failNamespaceName = "fail-closed-namesapce"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serverWebhookVersion = utilversion.MustParseSemantic("v1.8.0")
|
var serverWebhookVersion = utilversion.MustParseSemantic("v1.8.0")
|
||||||
|
@ -96,7 +99,7 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
|
||||||
// Note that in 1.9 we will have backwards incompatible change to
|
// Note that in 1.9 we will have backwards incompatible change to
|
||||||
// admission webhooks, so the image will be updated to 1.9 sometime in
|
// admission webhooks, so the image will be updated to 1.9 sometime in
|
||||||
// the development 1.9 cycle.
|
// the development 1.9 cycle.
|
||||||
deployWebhookAndService(f, "gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v5", context)
|
deployWebhookAndService(f, "gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v6", context)
|
||||||
})
|
})
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
cleanWebhookTest(client, namespaceName)
|
cleanWebhookTest(client, namespaceName)
|
||||||
|
@ -121,6 +124,26 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
|
||||||
err := f.ClientSet.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(webhookFailClosedConfigName, nil)
|
err := f.ClientSet.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(webhookFailClosedConfigName, nil)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed deleting fail closed webhook, this may cause subsequent e2e tests to fail")
|
Expect(err).NotTo(HaveOccurred(), "failed deleting fail closed webhook, this may cause subsequent e2e tests to fail")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Should mutate configmap", func() {
|
||||||
|
registerMutatingWebhookForConfigMap(f, context)
|
||||||
|
testMutatingConfigMapWebhook(f)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should mutate crd", func() {
|
||||||
|
crdCleanup, dynamicClient := createCRD(f)
|
||||||
|
defer crdCleanup()
|
||||||
|
registerMutatingWebhookForCRD(f, context)
|
||||||
|
testMutatingCRDWebhook(f, dynamicClient)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: add more e2e tests for mutating webhooks
|
||||||
|
// 1. mutating webhook that mutates pod
|
||||||
|
// 2. mutating webhook that sends empty patch
|
||||||
|
// 2.1 and sets status.allowed=true
|
||||||
|
// 2.2 and sets status.allowed=false
|
||||||
|
// 3. mutating webhook that sends patch, but also sets status.allowed=false
|
||||||
|
// 4. mtuating webhook that fail-open v.s. fail-closed
|
||||||
})
|
})
|
||||||
|
|
||||||
func createAuthReaderRoleBinding(f *framework.Framework, namespace string) {
|
func createAuthReaderRoleBinding(f *framework.Framework, namespace string) {
|
||||||
|
@ -340,6 +363,78 @@ func registerWebhook(f *framework.Framework, context *certContext) {
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerMutatingWebhookForConfigMap(f *framework.Framework, context *certContext) {
|
||||||
|
client := f.ClientSet
|
||||||
|
By("Registering the mutating configmap webhook via the AdmissionRegistration API")
|
||||||
|
|
||||||
|
namespace := f.Namespace.Name
|
||||||
|
|
||||||
|
_, err := client.AdmissionregistrationV1alpha1().MutatingWebhookConfigurations().Create(&v1alpha1.MutatingWebhookConfiguration{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: mutatingWebhookConfigName,
|
||||||
|
},
|
||||||
|
Webhooks: []v1alpha1.Webhook{
|
||||||
|
{
|
||||||
|
Name: "adding-configmap-data-stage-1.k8s.io",
|
||||||
|
Rules: []v1alpha1.RuleWithOperations{{
|
||||||
|
Operations: []v1alpha1.OperationType{v1alpha1.Create},
|
||||||
|
Rule: v1alpha1.Rule{
|
||||||
|
APIGroups: []string{""},
|
||||||
|
APIVersions: []string{"v1"},
|
||||||
|
Resources: []string{"configmaps"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
ClientConfig: v1alpha1.WebhookClientConfig{
|
||||||
|
Service: &v1alpha1.ServiceReference{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: serviceName,
|
||||||
|
Path: strPtr("/mutating-configmaps"),
|
||||||
|
},
|
||||||
|
CABundle: context.signingCert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "adding-configmap-data-stage-2.k8s.io",
|
||||||
|
Rules: []v1alpha1.RuleWithOperations{{
|
||||||
|
Operations: []v1alpha1.OperationType{v1alpha1.Create},
|
||||||
|
Rule: v1alpha1.Rule{
|
||||||
|
APIGroups: []string{""},
|
||||||
|
APIVersions: []string{"v1"},
|
||||||
|
Resources: []string{"configmaps"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
ClientConfig: v1alpha1.WebhookClientConfig{
|
||||||
|
Service: &v1alpha1.ServiceReference{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: serviceName,
|
||||||
|
Path: strPtr("/mutating-configmaps"),
|
||||||
|
},
|
||||||
|
CABundle: context.signingCert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", mutatingWebhookConfigName, namespace)
|
||||||
|
|
||||||
|
// The webhook configuration is honored in 1s.
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
func testMutatingConfigMapWebhook(f *framework.Framework) {
|
||||||
|
By("create a configmap that should be updated by the webhook")
|
||||||
|
client := f.ClientSet
|
||||||
|
configMap := toBeMutatedConfigMap(f)
|
||||||
|
mutatedConfigMap, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(configMap)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
expectedConfigMapData := map[string]string{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
"mutation-stage-1": "yes",
|
||||||
|
"mutation-stage-2": "yes",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expectedConfigMapData, mutatedConfigMap.Data) {
|
||||||
|
framework.Failf("\nexpected %#v\n, got %#v\n", expectedConfigMapData, mutatedConfigMap.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testWebhook(f *framework.Framework) {
|
func testWebhook(f *framework.Framework) {
|
||||||
By("create a pod that should be denied by the webhook")
|
By("create a pod that should be denied by the webhook")
|
||||||
client := f.ClientSet
|
client := f.ClientSet
|
||||||
|
@ -542,6 +637,17 @@ func nonCompliantConfigMap(f *framework.Framework) *v1.ConfigMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toBeMutatedConfigMap(f *framework.Framework) *v1.ConfigMap {
|
||||||
|
return &v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "to-be-mutated",
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func nonCompliantConfigMapPatch() string {
|
func nonCompliantConfigMapPatch() string {
|
||||||
return fmt.Sprint(`{"data":{"webhook-e2e-test":"webhook-disallow"}}`)
|
return fmt.Sprint(`{"data":{"webhook-e2e-test":"webhook-disallow"}}`)
|
||||||
}
|
}
|
||||||
|
@ -571,6 +677,7 @@ func updateConfigMap(c clientset.Interface, ns, name string, update updateConfig
|
||||||
func cleanWebhookTest(client clientset.Interface, namespaceName string) {
|
func cleanWebhookTest(client clientset.Interface, namespaceName string) {
|
||||||
_ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(webhookConfigName, nil)
|
_ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(webhookConfigName, nil)
|
||||||
_ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(crdWebhookConfigName, nil)
|
_ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(crdWebhookConfigName, nil)
|
||||||
|
_ = client.AdmissionregistrationV1alpha1().MutatingWebhookConfigurations().Delete(mutatingWebhookConfigName, nil)
|
||||||
_ = client.CoreV1().Services(namespaceName).Delete(serviceName, nil)
|
_ = client.CoreV1().Services(namespaceName).Delete(serviceName, nil)
|
||||||
_ = client.ExtensionsV1beta1().Deployments(namespaceName).Delete(deploymentName, nil)
|
_ = client.ExtensionsV1beta1().Deployments(namespaceName).Delete(deploymentName, nil)
|
||||||
_ = client.CoreV1().Secrets(namespaceName).Delete(secretName, nil)
|
_ = client.CoreV1().Secrets(namespaceName).Delete(secretName, nil)
|
||||||
|
@ -667,6 +774,62 @@ func registerWebhookForCRD(f *framework.Framework, context *certContext) {
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerMutatingWebhookForCRD(f *framework.Framework, context *certContext) {
|
||||||
|
client := f.ClientSet
|
||||||
|
By("Registering the mutating webhook for crd via the AdmissionRegistration API")
|
||||||
|
|
||||||
|
namespace := f.Namespace.Name
|
||||||
|
_, err := client.AdmissionregistrationV1alpha1().MutatingWebhookConfigurations().Create(&v1alpha1.MutatingWebhookConfiguration{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: crdMutatingWebhookConfigName,
|
||||||
|
},
|
||||||
|
Webhooks: []v1alpha1.Webhook{
|
||||||
|
{
|
||||||
|
Name: "mutate-crd-data-stage-1.k8s.io",
|
||||||
|
Rules: []v1alpha1.RuleWithOperations{{
|
||||||
|
Operations: []v1alpha1.OperationType{v1alpha1.Create},
|
||||||
|
Rule: v1alpha1.Rule{
|
||||||
|
APIGroups: []string{crdAPIGroup},
|
||||||
|
APIVersions: []string{crdAPIVersion},
|
||||||
|
Resources: []string{crdName + "s"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
ClientConfig: v1alpha1.WebhookClientConfig{
|
||||||
|
Service: &v1alpha1.ServiceReference{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: serviceName,
|
||||||
|
Path: strPtr("/mutating-crd"),
|
||||||
|
},
|
||||||
|
CABundle: context.signingCert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "mutate-crd-data-stage-2.k8s.io",
|
||||||
|
Rules: []v1alpha1.RuleWithOperations{{
|
||||||
|
Operations: []v1alpha1.OperationType{v1alpha1.Create},
|
||||||
|
Rule: v1alpha1.Rule{
|
||||||
|
APIGroups: []string{crdAPIGroup},
|
||||||
|
APIVersions: []string{crdAPIVersion},
|
||||||
|
Resources: []string{crdName + "s"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
ClientConfig: v1alpha1.WebhookClientConfig{
|
||||||
|
Service: &v1alpha1.ServiceReference{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: serviceName,
|
||||||
|
Path: strPtr("/mutating-crd"),
|
||||||
|
},
|
||||||
|
CABundle: context.signingCert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
framework.ExpectNoError(err, "registering crd webhook config %s with namespace %s", webhookConfigName, namespace)
|
||||||
|
|
||||||
|
// The webhook configuration is honored in 1s.
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
func testCRDWebhook(f *framework.Framework, crdClient dynamic.ResourceInterface) {
|
func testCRDWebhook(f *framework.Framework, crdClient dynamic.ResourceInterface) {
|
||||||
By("Creating a custom resource that should be denied by the webhook")
|
By("Creating a custom resource that should be denied by the webhook")
|
||||||
crd := newCRDForAdmissionWebhookTest()
|
crd := newCRDForAdmissionWebhookTest()
|
||||||
|
@ -690,3 +853,31 @@ func testCRDWebhook(f *framework.Framework, crdClient dynamic.ResourceInterface)
|
||||||
framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
|
framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testMutatingCRDWebhook(f *framework.Framework, crdClient dynamic.ResourceInterface) {
|
||||||
|
By("Creating a custom resource that should be mutated by the webhook")
|
||||||
|
crd := newCRDForAdmissionWebhookTest()
|
||||||
|
cr := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": crd.Spec.Names.Kind,
|
||||||
|
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version,
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cr-instance-1",
|
||||||
|
"namespace": f.Namespace.Name,
|
||||||
|
},
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mutatedCR, err := crdClient.Create(cr)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
expectedCRData := map[string]interface{}{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
"mutation-stage-1": "yes",
|
||||||
|
"mutation-stage-2": "yes",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
|
||||||
|
framework.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
build:
|
build:
|
||||||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook .
|
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook .
|
||||||
docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v5 .
|
docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v6 .
|
||||||
rm -rf webhook
|
rm -rf webhook
|
||||||
push:
|
push:
|
||||||
gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v5
|
gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v6
|
||||||
|
|
|
@ -28,6 +28,18 @@ import (
|
||||||
"k8s.io/api/admission/v1alpha1"
|
"k8s.io/api/admission/v1alpha1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
// TODO: try this library to see if it generates correct json patch
|
||||||
|
// https://github.com/mattbaird/jsonpatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
patch1 string = `[
|
||||||
|
{ "op": "add", "path": "/data/mutation-stage-1", "value": "yes" }
|
||||||
|
]`
|
||||||
|
patch2 string = `[
|
||||||
|
{ "op": "add", "path": "/data/mutation-stage-2", "value": "yes" }
|
||||||
|
]`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains the server (the webhook) cert and key.
|
// Config contains the server (the webhook) cert and key.
|
||||||
|
@ -120,6 +132,64 @@ func admitConfigMaps(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
|
||||||
return &reviewResponse
|
return &reviewResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mutateConfigmaps(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
|
||||||
|
glog.V(2).Info("mutating configmaps")
|
||||||
|
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
|
||||||
|
if ar.Request.Resource != configMapResource {
|
||||||
|
glog.Errorf("expect resource to be %s", configMapResource)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := ar.Request.Object.Raw
|
||||||
|
configmap := corev1.ConfigMap{}
|
||||||
|
deserializer := codecs.UniversalDeserializer()
|
||||||
|
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
return toAdmissionResponse(err)
|
||||||
|
}
|
||||||
|
reviewResponse := v1alpha1.AdmissionResponse{}
|
||||||
|
reviewResponse.Allowed = true
|
||||||
|
if configmap.Data["mutation-start"] == "yes" {
|
||||||
|
reviewResponse.Patch = []byte(patch1)
|
||||||
|
}
|
||||||
|
if configmap.Data["mutation-stage-1"] == "yes" {
|
||||||
|
reviewResponse.Patch = []byte(patch2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pt := v1alpha1.PatchTypeJSONPatch
|
||||||
|
reviewResponse.PatchType = &pt
|
||||||
|
|
||||||
|
return &reviewResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func mutateCRD(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
|
||||||
|
glog.V(2).Info("mutating crd")
|
||||||
|
cr := struct {
|
||||||
|
metav1.ObjectMeta
|
||||||
|
Data map[string]string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
raw := ar.Request.Object.Raw
|
||||||
|
err := json.Unmarshal(raw, &cr)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
return toAdmissionResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reviewResponse := v1alpha1.AdmissionResponse{}
|
||||||
|
reviewResponse.Allowed = true
|
||||||
|
|
||||||
|
if cr.Data["mutation-start"] == "yes" {
|
||||||
|
reviewResponse.Patch = []byte(patch1)
|
||||||
|
}
|
||||||
|
if cr.Data["mutation-stage-1"] == "yes" {
|
||||||
|
reviewResponse.Patch = []byte(patch2)
|
||||||
|
}
|
||||||
|
pt := v1alpha1.PatchTypeJSONPatch
|
||||||
|
reviewResponse.PatchType = &pt
|
||||||
|
return &reviewResponse
|
||||||
|
}
|
||||||
|
|
||||||
func admitCRD(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
|
func admitCRD(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
|
||||||
glog.V(2).Info("admitting crd")
|
glog.V(2).Info("admitting crd")
|
||||||
cr := struct {
|
cr := struct {
|
||||||
|
@ -179,6 +249,9 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
|
||||||
response.Response = reviewResponse
|
response.Response = reviewResponse
|
||||||
response.Response.UID = ar.Request.UID
|
response.Response.UID = ar.Request.UID
|
||||||
}
|
}
|
||||||
|
// reset the Object and OldObject, they are not needed in a response.
|
||||||
|
ar.Request.Object = runtime.RawExtension{}
|
||||||
|
ar.Request.OldObject = runtime.RawExtension{}
|
||||||
|
|
||||||
resp, err := json.Marshal(response)
|
resp, err := json.Marshal(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -197,10 +270,18 @@ func serveConfigmaps(w http.ResponseWriter, r *http.Request) {
|
||||||
serve(w, r, admitConfigMaps)
|
serve(w, r, admitConfigMaps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveMutateConfigmaps(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serve(w, r, mutateConfigmaps)
|
||||||
|
}
|
||||||
|
|
||||||
func serveCRD(w http.ResponseWriter, r *http.Request) {
|
func serveCRD(w http.ResponseWriter, r *http.Request) {
|
||||||
serve(w, r, admitCRD)
|
serve(w, r, admitCRD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveMutateCRD(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serve(w, r, mutateCRD)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var config Config
|
var config Config
|
||||||
config.addFlags()
|
config.addFlags()
|
||||||
|
@ -208,7 +289,9 @@ func main() {
|
||||||
|
|
||||||
http.HandleFunc("/pods", servePods)
|
http.HandleFunc("/pods", servePods)
|
||||||
http.HandleFunc("/configmaps", serveConfigmaps)
|
http.HandleFunc("/configmaps", serveConfigmaps)
|
||||||
|
http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
|
||||||
http.HandleFunc("/crd", serveCRD)
|
http.HandleFunc("/crd", serveCRD)
|
||||||
|
http.HandleFunc("/mutating-crd", serveMutateCRD)
|
||||||
clientset := getClient()
|
clientset := getClient()
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: ":443",
|
Addr: ":443",
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJSONPatchForConfigMap(t *testing.T) {
|
||||||
|
cm := corev1.ConfigMap{
|
||||||
|
Data: map[string]string{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmJS, err := json.Marshal(cm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchObj, err := jsonpatch.DecodePatch([]byte(patch1))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
patchedJS, err := patchObj.Apply(cmJS)
|
||||||
|
patchedObj := corev1.ConfigMap{}
|
||||||
|
err = json.Unmarshal(patchedJS, &patchedObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := corev1.ConfigMap{
|
||||||
|
Data: map[string]string{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
"mutation-stage-1": "yes",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(patchedObj, expected) {
|
||||||
|
t.Errorf("\nexpected %#v\n, got %#v", expected, patchedObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONPatchForUnstructured(t *testing.T) {
|
||||||
|
cr := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "Something",
|
||||||
|
"apiVersion": "somegroup/v1",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
crJS, err := json.Marshal(cr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchObj, err := jsonpatch.DecodePatch([]byte(patch1))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
patchedJS, err := patchObj.Apply(crJS)
|
||||||
|
patchedObj := unstructured.Unstructured{}
|
||||||
|
err = json.Unmarshal(patchedJS, &patchedObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expectedData := map[string]interface{}{
|
||||||
|
"mutation-start": "yes",
|
||||||
|
"mutation-stage-1": "yes",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(patchedObj.Object["data"], expectedData) {
|
||||||
|
t.Errorf("\nexpected %#v\n, got %#v", expectedData, patchedObj.Object["data"])
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue