mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-12-24 04:17:30 +00:00
move webhook admission to generic apiserver
This commit is contained in:
@@ -86,11 +86,11 @@ func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) {
|
||||
whiteList: whiteList,
|
||||
}
|
||||
|
||||
genericPluginInitializer, err := initializer.New(nil, nil, fakeAuthorizer{}, nil, nil)
|
||||
genericPluginInitializer, err := initializer.New(nil, nil, fakeAuthorizer{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(nil, nil, nil, legacyscheme.Registry.RESTMapper(), nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(nil, nil, nil, legacyscheme.Registry.RESTMapper(), nil, nil, nil)
|
||||
initializersChain := admission.PluginInitializers{}
|
||||
initializersChain = append(initializersChain, genericPluginInitializer)
|
||||
initializersChain = append(initializersChain, pluginInitializer)
|
||||
|
||||
@@ -744,7 +744,7 @@ func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.Sh
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.Validate(handler)
|
||||
return handler, f, err
|
||||
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewProvision()
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.Validate(handler)
|
||||
return handler, f, err
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewExists()
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.Validate(handler)
|
||||
return handler, f, err
|
||||
|
||||
@@ -241,7 +241,7 @@ func TestIgnoreUpdatingInitializedPod(t *testing.T) {
|
||||
func newHandlerForTest(c clientset.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewPodNodeSelector(nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.Validate(handler)
|
||||
return handler, f, err
|
||||
|
||||
@@ -342,7 +342,7 @@ func newHandlerForTest(c clientset.Interface) (*podTolerationsPlugin, informers.
|
||||
return nil, nil, err
|
||||
}
|
||||
handler := NewPodTolerationsPlugin(pluginConfig)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.Validate(handler)
|
||||
return handler, f, err
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"admissionreview.go",
|
||||
"authentication.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
"rules.go",
|
||||
"serviceresolver.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/webhook",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/apis/admission/install:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//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/api/authentication/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/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/apimachinery/pkg/util/yaml: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/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"authentication_test.go",
|
||||
"certs_test.go",
|
||||
"rules_test.go",
|
||||
"serviceresolver_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/webhook",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/admission/install: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/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api: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"],
|
||||
)
|
||||
@@ -1,300 +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.
|
||||
*/
|
||||
|
||||
// Package webhook delegates admission checks to dynamically configured webhooks.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"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"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
|
||||
// install the clientgo admission API for use with api registry
|
||||
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
||||
)
|
||||
|
||||
type ErrCallingWebhook struct {
|
||||
WebhookName string
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (e *ErrCallingWebhook) Error() string {
|
||||
if e.Reason != nil {
|
||||
return fmt.Sprintf("failed calling admission webhook %q: %v", e.WebhookName, e.Reason)
|
||||
}
|
||||
return fmt.Sprintf("failed calling admission webhook %q; no further details available", e.WebhookName)
|
||||
}
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("GenericAdmissionWebhook", func(configFile io.Reader) (admission.Interface, error) {
|
||||
plugin, err := NewGenericAdmissionWebhook(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
// WebhookSource can list dynamic webhook plugins.
|
||||
type WebhookSource interface {
|
||||
Run(stopCh <-chan struct{})
|
||||
ExternalAdmissionHooks() (*v1alpha1.ExternalAdmissionHookConfiguration, error)
|
||||
}
|
||||
|
||||
// NewGenericAdmissionWebhook returns a generic admission webhook plugin.
|
||||
func NewGenericAdmissionWebhook(configFile io.Reader) (*GenericAdmissionWebhook, error) {
|
||||
kubeconfigFile := ""
|
||||
if configFile != nil {
|
||||
// TODO: move this to a versioned configuration file format
|
||||
var config AdmissionConfig
|
||||
d := yaml.NewYAMLOrJSONDecoder(configFile, 4096)
|
||||
err := d.Decode(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kubeconfigFile = config.KubeConfigFile
|
||||
}
|
||||
authInfoResolver, err := newDefaultAuthenticationInfoResolver(kubeconfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GenericAdmissionWebhook{
|
||||
Handler: admission.NewHandler(
|
||||
admission.Connect,
|
||||
admission.Create,
|
||||
admission.Delete,
|
||||
admission.Update,
|
||||
),
|
||||
authInfoResolver: authInfoResolver,
|
||||
serviceResolver: defaultServiceResolver{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenericAdmissionWebhook is an implementation of admission.Interface.
|
||||
type GenericAdmissionWebhook struct {
|
||||
*admission.Handler
|
||||
hookSource WebhookSource
|
||||
serviceResolver admissioninit.ServiceResolver
|
||||
negotiatedSerializer runtime.NegotiatedSerializer
|
||||
|
||||
authInfoResolver AuthenticationInfoResolver
|
||||
}
|
||||
|
||||
var (
|
||||
_ = admissioninit.WantsServiceResolver(&GenericAdmissionWebhook{})
|
||||
_ = genericadmissioninit.WantsWebhookRESTClientConfig(&GenericAdmissionWebhook{})
|
||||
_ = genericadmissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{})
|
||||
)
|
||||
|
||||
// TODO find a better way wire this, but keep this pull small for now.
|
||||
func (a *GenericAdmissionWebhook) SetWebhookRESTClientConfig(in *rest.Config) {
|
||||
if in != nil {
|
||||
a.authInfoResolver = &dialOverridingAuthenticationInfoResolver{
|
||||
dialFn: in.Dial,
|
||||
delegate: a.authInfoResolver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *GenericAdmissionWebhook) SetServiceResolver(sr admissioninit.ServiceResolver) {
|
||||
if sr != nil {
|
||||
a.serviceResolver = sr
|
||||
}
|
||||
}
|
||||
|
||||
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
|
||||
func (a *GenericAdmissionWebhook) SetScheme(scheme *runtime.Scheme) {
|
||||
if scheme != nil {
|
||||
a.negotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{
|
||||
Serializer: serializer.NewCodecFactory(scheme).LegacyCodec(admissionv1alpha1.SchemeGroupVersion),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Interface) {
|
||||
a.hookSource = configuration.NewExternalAdmissionHookConfigurationManager(client.Admissionregistration().ExternalAdmissionHookConfigurations())
|
||||
}
|
||||
|
||||
func (a *GenericAdmissionWebhook) Validate() error {
|
||||
if a.hookSource == nil {
|
||||
return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a Kubernetes client to be provided")
|
||||
}
|
||||
if a.negotiatedSerializer == nil {
|
||||
return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a runtime.Scheme to be provided to derive a serializer")
|
||||
}
|
||||
go a.hookSource.Run(wait.NeverStop)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *GenericAdmissionWebhook) loadConfiguration(attr admission.Attributes) (*v1alpha1.ExternalAdmissionHookConfiguration, error) {
|
||||
hookConfig, err := a.hookSource.ExternalAdmissionHooks()
|
||||
// if ExternalAdmissionHook configuration is disabled, fail open
|
||||
if err == configuration.ErrDisabled {
|
||||
return &v1alpha1.ExternalAdmissionHookConfiguration{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
e := apierrors.NewServerTimeout(attr.GetResource().GroupResource(), string(attr.GetOperation()), 1)
|
||||
e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the ExternalAdmissionHook configuration: %v", err)
|
||||
e.ErrStatus.Reason = "LoadingConfiguration"
|
||||
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: "ExternalAdmissionHookConfigurationFailure",
|
||||
Message: "An error has occurred while refreshing the externalAdmissionHook 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 *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
|
||||
hookConfig, err := a.loadConfiguration(attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hooks := hookConfig.ExternalAdmissionHooks
|
||||
ctx := context.TODO()
|
||||
|
||||
errCh := make(chan error, len(hooks))
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(hooks))
|
||||
for i := range hooks {
|
||||
go func(hook *v1alpha1.ExternalAdmissionHook) {
|
||||
defer wg.Done()
|
||||
|
||||
err := a.callHook(ctx, hook, attr)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1alpha1.Ignore
|
||||
if callErr, ok := err.(*ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
utilruntime.HandleError(callErr)
|
||||
// Since we are failing open to begin with, we do not send an error down the channel
|
||||
return
|
||||
}
|
||||
|
||||
glog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
glog.Warningf("rejected by webhook %v %t: %v", hook.Name, err, err)
|
||||
errCh <- err
|
||||
}(&hooks[i])
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
|
||||
var errs []error
|
||||
for e := range errCh {
|
||||
errs = append(errs, e)
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(errs) > 1 {
|
||||
for i := 1; i < len(errs); i++ {
|
||||
// TODO: merge status errors; until then, just return the first one.
|
||||
utilruntime.HandleError(errs[i])
|
||||
}
|
||||
}
|
||||
return errs[0]
|
||||
}
|
||||
|
||||
func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.ExternalAdmissionHook, attr admission.Attributes) error {
|
||||
matches := false
|
||||
for _, r := range h.Rules {
|
||||
m := RuleMatcher{Rule: r, Attr: attr}
|
||||
if m.Matches() {
|
||||
matches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matches {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make the webhook request
|
||||
request := createAdmissionReview(attr)
|
||||
client, err := a.hookClient(h)
|
||||
if err != nil {
|
||||
return &ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
response := &admissionv1alpha1.AdmissionReview{}
|
||||
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
|
||||
return &ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
|
||||
if response.Status.Allowed {
|
||||
return nil
|
||||
}
|
||||
|
||||
if response.Status.Result == nil {
|
||||
return fmt.Errorf("admission webhook %q denied the request without explanation", h.Name)
|
||||
}
|
||||
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: *response.Status.Result,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook) (*rest.RESTClient, error) {
|
||||
serverName := h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc"
|
||||
u, err := a.serviceResolver.ResolveEndpoint(h.ClientConfig.Service.Namespace, h.ClientConfig.Service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: cache these instead of constructing one each time
|
||||
restConfig, err := a.authInfoResolver.ClientConfigFor(serverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := rest.CopyConfig(restConfig)
|
||||
cfg.Host = u.Host
|
||||
cfg.APIPath = path.Join(u.Path, h.ClientConfig.URLPath)
|
||||
cfg.TLSClientConfig.ServerName = serverName
|
||||
cfg.TLSClientConfig.CAData = h.ClientConfig.CABundle
|
||||
cfg.ContentConfig.NegotiatedSerializer = a.negotiatedSerializer
|
||||
return rest.UnversionedRESTClientFor(cfg)
|
||||
}
|
||||
@@ -1,333 +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.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/admission/v1alpha1"
|
||||
registrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
||||
)
|
||||
|
||||
type fakeHookSource struct {
|
||||
hooks []registrationv1alpha1.ExternalAdmissionHook
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeHookSource) ExternalAdmissionHooks() (*registrationv1alpha1.ExternalAdmissionHookConfiguration, error) {
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
return ®istrationv1alpha1.ExternalAdmissionHookConfiguration{ExternalAdmissionHooks: 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
|
||||
}
|
||||
|
||||
// TestAdmit tests that GenericAdmissionWebhook#Admit works as expected
|
||||
func TestAdmit(t *testing.T) {
|
||||
// Create the test webhook server
|
||||
sCert, err := tls.X509KeyPair(serverCert, serverKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
testServer := httptest.NewUnstartedServer(http.HandlerFunc(webhookHandler))
|
||||
testServer.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{sCert},
|
||||
ClientCAs: rootCAs,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
}
|
||||
testServer.StartTLS()
|
||||
defer testServer.Close()
|
||||
serverURL, err := url.ParseRequestURI(testServer.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("this should never happen? %v", err)
|
||||
}
|
||||
wh, err := NewGenericAdmissionWebhook(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wh.authInfoResolver = &fakeAuthenticationInfoResolver{
|
||||
restConfig: &rest.Config{
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CAData: caCert,
|
||||
CertData: clientCert,
|
||||
KeyData: clientKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Set up a test object for the call
|
||||
kind := api.Kind("Pod").WithVersion("v1")
|
||||
name := "my-pod"
|
||||
namespace := "webhook-test"
|
||||
object := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"pod.name": name,
|
||||
},
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
}
|
||||
oldObject := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
}
|
||||
operation := admission.Update
|
||||
resource := api.Resource("pods").WithVersion("v1")
|
||||
subResource := ""
|
||||
userInfo := user.DefaultInfo{
|
||||
Name: "webhook-test",
|
||||
UID: "webhook-test",
|
||||
}
|
||||
|
||||
type test struct {
|
||||
hookSource fakeHookSource
|
||||
path string
|
||||
expectAllow bool
|
||||
errorContains string
|
||||
}
|
||||
ccfg := func(urlPath string) registrationv1alpha1.AdmissionHookClientConfig {
|
||||
return registrationv1alpha1.AdmissionHookClientConfig{
|
||||
Service: registrationv1alpha1.ServiceReference{
|
||||
Name: "webhook-test",
|
||||
Namespace: "default",
|
||||
},
|
||||
URLPath: urlPath,
|
||||
CABundle: caCert,
|
||||
}
|
||||
}
|
||||
|
||||
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.ExternalAdmissionHook{{
|
||||
Name: "nomatch",
|
||||
ClientConfig: ccfg("disallow"),
|
||||
Rules: []registrationv1alpha1.RuleWithOperations{{
|
||||
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
expectAllow: true,
|
||||
},
|
||||
"match & allow": {
|
||||
hookSource: fakeHookSource{
|
||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||
Name: "allow",
|
||||
ClientConfig: ccfg("allow"),
|
||||
Rules: matchEverythingRules,
|
||||
}},
|
||||
},
|
||||
expectAllow: true,
|
||||
},
|
||||
"match & disallow": {
|
||||
hookSource: fakeHookSource{
|
||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||
Name: "disallow",
|
||||
ClientConfig: ccfg("disallow"),
|
||||
Rules: matchEverythingRules,
|
||||
}},
|
||||
},
|
||||
errorContains: "without explanation",
|
||||
},
|
||||
"match & disallow ii": {
|
||||
hookSource: fakeHookSource{
|
||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||
Name: "disallowReason",
|
||||
ClientConfig: ccfg("disallowReason"),
|
||||
Rules: matchEverythingRules,
|
||||
}},
|
||||
},
|
||||
errorContains: "you shall not pass",
|
||||
},
|
||||
"match & fail (but allow because fail open)": {
|
||||
hookSource: fakeHookSource{
|
||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||
Name: "internalErr A",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
FailurePolicy: &policyIgnore,
|
||||
}, {
|
||||
Name: "internalErr B",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
FailurePolicy: &policyIgnore,
|
||||
}, {
|
||||
Name: "internalErr C",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
FailurePolicy: &policyIgnore,
|
||||
}},
|
||||
},
|
||||
expectAllow: true,
|
||||
},
|
||||
"match & fail (but disallow because fail closed on nil)": {
|
||||
hookSource: fakeHookSource{
|
||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||
Name: "internalErr A",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
}, {
|
||||
Name: "internalErr B",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
}, {
|
||||
Name: "internalErr C",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
}},
|
||||
},
|
||||
expectAllow: false,
|
||||
},
|
||||
"match & fail (but fail because fail closed)": {
|
||||
hookSource: fakeHookSource{
|
||||
hooks: []registrationv1alpha1.ExternalAdmissionHook{{
|
||||
Name: "internalErr A",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
FailurePolicy: &policyFail,
|
||||
}, {
|
||||
Name: "internalErr B",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
FailurePolicy: &policyFail,
|
||||
}, {
|
||||
Name: "internalErr C",
|
||||
ClientConfig: ccfg("internalErr"),
|
||||
Rules: matchEverythingRules,
|
||||
FailurePolicy: &policyFail,
|
||||
}},
|
||||
},
|
||||
expectAllow: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range table {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
wh.hookSource = &tt.hookSource
|
||||
wh.serviceResolver = fakeServiceResolver{base: *serverURL}
|
||||
wh.SetScheme(legacyscheme.Scheme)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
Status: v1alpha1.AdmissionReviewStatus{
|
||||
Allowed: false,
|
||||
},
|
||||
})
|
||||
case "/disallowReason":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{
|
||||
Status: v1alpha1.AdmissionReviewStatus{
|
||||
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{
|
||||
Status: v1alpha1.AdmissionReviewStatus{
|
||||
Allowed: true,
|
||||
},
|
||||
})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAuthenticationInfoResolver struct {
|
||||
restConfig *rest.Config
|
||||
}
|
||||
|
||||
func (c *fakeAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) {
|
||||
return c.restConfig, nil
|
||||
}
|
||||
@@ -1,70 +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.
|
||||
*/
|
||||
|
||||
// Package webhook delegates admission checks to dynamically configured webhooks.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
admissionv1alpha1 "k8s.io/api/admission/v1alpha1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// createAdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func createAdmissionReview(attr admission.Attributes) admissionv1alpha1.AdmissionReview {
|
||||
gvk := attr.GetKind()
|
||||
gvr := attr.GetResource()
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
|
||||
return admissionv1alpha1.AdmissionReview{
|
||||
Spec: admissionv1alpha1.AdmissionReviewSpec{
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: attr.GetSubresource(),
|
||||
Operation: admissionv1alpha1.Operation(attr.GetOperation()),
|
||||
Object: runtime.RawExtension{
|
||||
Object: attr.GetObject(),
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Object: attr.GetOldObject(),
|
||||
},
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
UserInfo: userInfo,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,157 +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.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
type AuthenticationInfoResolver interface {
|
||||
ClientConfigFor(server string) (*rest.Config, error)
|
||||
}
|
||||
|
||||
type defaultAuthenticationInfoResolver struct {
|
||||
kubeconfig clientcmdapi.Config
|
||||
}
|
||||
|
||||
func newDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) {
|
||||
if len(kubeconfigFile) == 0 {
|
||||
return &defaultAuthenticationInfoResolver{}, nil
|
||||
}
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeconfigFile
|
||||
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||
clientConfig, err := loader.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &defaultAuthenticationInfoResolver{kubeconfig: clientConfig}, nil
|
||||
}
|
||||
|
||||
func (c *defaultAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) {
|
||||
// exact match
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos[server]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
|
||||
// star prefixed match
|
||||
serverSteps := strings.Split(server, ".")
|
||||
for i := 1; i < len(serverSteps); i++ {
|
||||
nickName := "*." + strings.Join(serverSteps[i:], ".")
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// if we're trying to hit the kube-apiserver and there wasn't an explicit config, use the in-cluster config
|
||||
if server == "kubernetes.default.svc" {
|
||||
// if we can find an in-cluster-config use that. If we can't, fall through.
|
||||
inClusterConfig, err := rest.InClusterConfig()
|
||||
if err == nil {
|
||||
return setGlobalDefaults(inClusterConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
// star (default) match
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos["*"]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
|
||||
// use the current context from the kubeconfig if possible
|
||||
if len(c.kubeconfig.CurrentContext) > 0 {
|
||||
if currContext, ok := c.kubeconfig.Contexts[c.kubeconfig.CurrentContext]; ok {
|
||||
if len(currContext.AuthInfo) > 0 {
|
||||
if currAuth, ok := c.kubeconfig.AuthInfos[currContext.AuthInfo]; ok {
|
||||
return restConfigFromKubeconfig(currAuth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// anonymous
|
||||
return setGlobalDefaults(&rest.Config{}), nil
|
||||
}
|
||||
|
||||
func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Config, error) {
|
||||
config := &rest.Config{}
|
||||
|
||||
// blindly overwrite existing values based on precedence
|
||||
if len(configAuthInfo.Token) > 0 {
|
||||
config.BearerToken = configAuthInfo.Token
|
||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.BearerToken = string(tokenBytes)
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
config.Impersonate = rest.ImpersonationConfig{
|
||||
UserName: configAuthInfo.Impersonate,
|
||||
Groups: configAuthInfo.ImpersonateGroups,
|
||||
Extra: configAuthInfo.ImpersonateUserExtra,
|
||||
}
|
||||
}
|
||||
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
||||
config.CertFile = configAuthInfo.ClientCertificate
|
||||
config.CertData = configAuthInfo.ClientCertificateData
|
||||
config.KeyFile = configAuthInfo.ClientKey
|
||||
config.KeyData = configAuthInfo.ClientKeyData
|
||||
}
|
||||
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
||||
config.Username = configAuthInfo.Username
|
||||
config.Password = configAuthInfo.Password
|
||||
}
|
||||
if configAuthInfo.AuthProvider != nil {
|
||||
return nil, fmt.Errorf("auth provider not supported")
|
||||
}
|
||||
|
||||
return setGlobalDefaults(config), nil
|
||||
}
|
||||
|
||||
func setGlobalDefaults(config *rest.Config) *rest.Config {
|
||||
config.UserAgent = "kube-apiserver-admission"
|
||||
config.Timeout = 30 * time.Second
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type dialOverridingAuthenticationInfoResolver struct {
|
||||
dialFn func(network, addr string) (net.Conn, error)
|
||||
delegate AuthenticationInfoResolver
|
||||
}
|
||||
|
||||
func (c *dialOverridingAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) {
|
||||
cfg, err := c.delegate.ClientConfigFor(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.Dial = c.dialFn
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -1,130 +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.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/rest"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func TestAuthenticationDetection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeconfig clientcmdapi.Config
|
||||
serverName string
|
||||
expected rest.Config
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
serverName: "foo.com",
|
||||
},
|
||||
{
|
||||
name: "fallback to current context",
|
||||
serverName: "foo.com",
|
||||
kubeconfig: clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"bar.com": {Token: "bar"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"ctx": {
|
||||
AuthInfo: "bar.com",
|
||||
},
|
||||
},
|
||||
CurrentContext: "ctx",
|
||||
},
|
||||
expected: rest.Config{BearerToken: "bar"},
|
||||
},
|
||||
{
|
||||
name: "exact match",
|
||||
serverName: "foo.com",
|
||||
kubeconfig: clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"foo.com": {Token: "foo"},
|
||||
"*.com": {Token: "foo-star"},
|
||||
"bar.com": {Token: "bar"},
|
||||
},
|
||||
},
|
||||
expected: rest.Config{BearerToken: "foo"},
|
||||
},
|
||||
{
|
||||
name: "partial star match",
|
||||
serverName: "foo.com",
|
||||
kubeconfig: clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"*.com": {Token: "foo-star"},
|
||||
"bar.com": {Token: "bar"},
|
||||
},
|
||||
},
|
||||
expected: rest.Config{BearerToken: "foo-star"},
|
||||
},
|
||||
{
|
||||
name: "full star match",
|
||||
serverName: "foo.com",
|
||||
kubeconfig: clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"*": {Token: "star"},
|
||||
"bar.com": {Token: "bar"},
|
||||
},
|
||||
},
|
||||
expected: rest.Config{BearerToken: "star"},
|
||||
},
|
||||
{
|
||||
name: "skip bad in cluster config",
|
||||
serverName: "kubernetes.default.svc",
|
||||
kubeconfig: clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"*": {Token: "star"},
|
||||
"bar.com": {Token: "bar"},
|
||||
},
|
||||
},
|
||||
expected: rest.Config{BearerToken: "star"},
|
||||
},
|
||||
{
|
||||
name: "most selective",
|
||||
serverName: "one.two.three.com",
|
||||
kubeconfig: clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"*.two.three.com": {Token: "first"},
|
||||
"*.three.com": {Token: "second"},
|
||||
"*.com": {Token: "third"},
|
||||
},
|
||||
},
|
||||
expected: rest.Config{BearerToken: "first"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
resolver := defaultAuthenticationInfoResolver{kubeconfig: tc.kubeconfig}
|
||||
actual, err := resolver.ClientConfigFor(tc.serverName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual.UserAgent = ""
|
||||
actual.Timeout = 0
|
||||
|
||||
if !equality.Semantic.DeepEqual(*actual, tc.expected) {
|
||||
t.Errorf("%v", diff.ObjectReflectDiff(tc.expected, *actual))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 webhook
|
||||
|
||||
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-----`)
|
||||
@@ -1,22 +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.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
// AdmissionConfig holds config data that is unique to each API server.
|
||||
type AdmissionConfig struct {
|
||||
KubeConfigFile string `json:"kubeConfigFile"`
|
||||
}
|
||||
@@ -1,18 +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.
|
||||
*/
|
||||
|
||||
// Package webhook checks a webhook for configured operation admission
|
||||
package webhook // import "k8s.io/kubernetes/plugin/pkg/admission/webhook"
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 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.
|
||||
|
||||
set -e
|
||||
|
||||
# gencerts.sh generates the certificates for the generic webhook admission plugin tests.
|
||||
#
|
||||
# It is not expected to be run often (there is no go generate rule), and mainly
|
||||
# exists for documentation purposes.
|
||||
|
||||
CN_BASE="generic_webhook_admission_plugin_tests"
|
||||
|
||||
cat > server.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth, serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
DNS.1 = webhook-test.default.svc
|
||||
EOF
|
||||
|
||||
cat > client.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth, serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
DNS.1 = webhook-test.default.svc
|
||||
EOF
|
||||
|
||||
# Create a certificate authority
|
||||
openssl genrsa -out caKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=${CN_BASE}_ca"
|
||||
|
||||
# Create a second certificate authority
|
||||
openssl genrsa -out badCAKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=${CN_BASE}_ca"
|
||||
|
||||
# Create a server certiticate
|
||||
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 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
|
||||
openssl genrsa -out clientKey.pem 2048
|
||||
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
|
||||
|
||||
outfile=certs_test.go
|
||||
|
||||
cat > $outfile << EOF
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
EOF
|
||||
|
||||
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
||||
echo "// and holds raw certificates for the webhook tests." >> $outfile
|
||||
echo "" >> $outfile
|
||||
echo "package webhook" >> $outfile
|
||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
||||
data=$(cat ${file}.pem)
|
||||
echo "" >> $outfile
|
||||
echo "var $file = []byte(\`$data\`)" >> $outfile
|
||||
done
|
||||
|
||||
# Clean up after we're done.
|
||||
rm *.pem
|
||||
rm *.csr
|
||||
rm *.srl
|
||||
rm *.conf
|
||||
@@ -1,94 +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.
|
||||
*/
|
||||
|
||||
// Package webhook checks a webhook for configured operation admission
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
type RuleMatcher struct {
|
||||
Rule v1alpha1.RuleWithOperations
|
||||
Attr admission.Attributes
|
||||
}
|
||||
|
||||
func (r *RuleMatcher) Matches() bool {
|
||||
return r.operation() &&
|
||||
r.group() &&
|
||||
r.version() &&
|
||||
r.resource()
|
||||
}
|
||||
|
||||
func exactOrWildcard(items []string, requested string) bool {
|
||||
for _, item := range items {
|
||||
if item == "*" {
|
||||
return true
|
||||
}
|
||||
if item == requested {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *RuleMatcher) group() bool {
|
||||
return exactOrWildcard(r.Rule.APIGroups, r.Attr.GetResource().Group)
|
||||
}
|
||||
|
||||
func (r *RuleMatcher) version() bool {
|
||||
return exactOrWildcard(r.Rule.APIVersions, r.Attr.GetResource().Version)
|
||||
}
|
||||
|
||||
func (r *RuleMatcher) operation() bool {
|
||||
attrOp := r.Attr.GetOperation()
|
||||
for _, op := range r.Rule.Operations {
|
||||
if op == v1alpha1.OperationAll {
|
||||
return true
|
||||
}
|
||||
// The constants are the same such that this is a valid cast (and this
|
||||
// is tested).
|
||||
if op == v1alpha1.OperationType(attrOp) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func splitResource(resSub string) (res, sub string) {
|
||||
parts := strings.SplitN(resSub, "/", 2)
|
||||
if len(parts) == 2 {
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
return parts[0], ""
|
||||
}
|
||||
|
||||
func (r *RuleMatcher) resource() bool {
|
||||
opRes, opSub := r.Attr.GetResource().Resource, r.Attr.GetSubresource()
|
||||
for _, res := range r.Rule.Resources {
|
||||
res, sub := splitResource(res)
|
||||
resMatch := res == "*" || res == opRes
|
||||
subMatch := sub == "*" || sub == opSub
|
||||
if resMatch && subMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,300 +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.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
adreg "k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
type ruleTest struct {
|
||||
rule adreg.RuleWithOperations
|
||||
match []admission.Attributes
|
||||
noMatch []admission.Attributes
|
||||
}
|
||||
type tests map[string]ruleTest
|
||||
|
||||
func a(group, version, resource, subresource, name string, operation admission.Operation) admission.Attributes {
|
||||
return admission.NewAttributesRecord(
|
||||
nil, nil,
|
||||
schema.GroupVersionKind{Group: group, Version: version, Kind: "k" + resource},
|
||||
"ns", name,
|
||||
schema.GroupVersionResource{Group: group, Version: version, Resource: resource}, subresource,
|
||||
operation,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
func attrList(a ...admission.Attributes) []admission.Attributes {
|
||||
return a
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
table := tests{
|
||||
"wildcard": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
APIGroups: []string{"*"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
"exact": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
APIGroups: []string{"g1", "g2"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g1", "v", "r", "", "name", admission.Create),
|
||||
a("g2", "v2", "r3", "", "name", admission.Create),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g3", "v", "r", "", "name", admission.Create),
|
||||
a("g4", "v", "r", "", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range table {
|
||||
for _, m := range tt.match {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if !r.group() {
|
||||
t.Errorf("%v: expected match %#v", name, m)
|
||||
}
|
||||
}
|
||||
for _, m := range tt.noMatch {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if r.group() {
|
||||
t.Errorf("%v: expected no match %#v", name, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
table := tests{
|
||||
"wildcard": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
APIVersions: []string{"*"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
"exact": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
APIVersions: []string{"v1", "v2"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g1", "v1", "r", "", "name", admission.Create),
|
||||
a("g2", "v2", "r", "", "name", admission.Create),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g1", "v3", "r", "", "name", admission.Create),
|
||||
a("g2", "v4", "r", "", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
}
|
||||
for name, tt := range table {
|
||||
for _, m := range tt.match {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if !r.version() {
|
||||
t.Errorf("%v: expected match %#v", name, m)
|
||||
}
|
||||
}
|
||||
for _, m := range tt.noMatch {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if r.version() {
|
||||
t.Errorf("%v: expected no match %#v", name, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperation(t *testing.T) {
|
||||
table := tests{
|
||||
"wildcard": {
|
||||
rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.OperationAll}},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("g", "v", "r", "", "name", admission.Update),
|
||||
a("g", "v", "r", "", "name", admission.Delete),
|
||||
a("g", "v", "r", "", "name", admission.Connect),
|
||||
),
|
||||
},
|
||||
"create": {
|
||||
rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Create}},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Update),
|
||||
a("g", "v", "r", "", "name", admission.Delete),
|
||||
a("g", "v", "r", "", "name", admission.Connect),
|
||||
),
|
||||
},
|
||||
"update": {
|
||||
rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Update}},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Update),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("g", "v", "r", "", "name", admission.Delete),
|
||||
a("g", "v", "r", "", "name", admission.Connect),
|
||||
),
|
||||
},
|
||||
"delete": {
|
||||
rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Delete}},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Delete),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("g", "v", "r", "", "name", admission.Update),
|
||||
a("g", "v", "r", "", "name", admission.Connect),
|
||||
),
|
||||
},
|
||||
"connect": {
|
||||
rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Connect}},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Connect),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("g", "v", "r", "", "name", admission.Update),
|
||||
a("g", "v", "r", "", "name", admission.Delete),
|
||||
),
|
||||
},
|
||||
"multiple": {
|
||||
rule: adreg.RuleWithOperations{Operations: []adreg.OperationType{adreg.Update, adreg.Delete}},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Update),
|
||||
a("g", "v", "r", "", "name", admission.Delete),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("g", "v", "r", "", "name", admission.Connect),
|
||||
),
|
||||
},
|
||||
}
|
||||
for name, tt := range table {
|
||||
for _, m := range tt.match {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if !r.operation() {
|
||||
t.Errorf("%v: expected match %#v", name, m)
|
||||
}
|
||||
}
|
||||
for _, m := range tt.noMatch {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if r.operation() {
|
||||
t.Errorf("%v: expected no match %#v", name, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResource(t *testing.T) {
|
||||
table := tests{
|
||||
"no subresources": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("2", "v", "r2", "", "name", admission.Create),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g", "v", "r", "exec", "name", admission.Create),
|
||||
a("2", "v", "r2", "proxy", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
"r & subresources": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
Resources: []string{"r/*"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("g", "v", "r", "exec", "name", admission.Create),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("2", "v", "r2", "", "name", admission.Create),
|
||||
a("2", "v", "r2", "proxy", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
"r & subresources or r2": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
Resources: []string{"r/*", "r2"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("g", "v", "r", "exec", "name", admission.Create),
|
||||
a("2", "v", "r2", "", "name", admission.Create),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("2", "v", "r2", "proxy", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
"proxy or exec": {
|
||||
rule: adreg.RuleWithOperations{
|
||||
Rule: adreg.Rule{
|
||||
Resources: []string{"*/proxy", "*/exec"},
|
||||
},
|
||||
},
|
||||
match: attrList(
|
||||
a("g", "v", "r", "exec", "name", admission.Create),
|
||||
a("2", "v", "r2", "proxy", "name", admission.Create),
|
||||
a("2", "v", "r3", "proxy", "name", admission.Create),
|
||||
),
|
||||
noMatch: attrList(
|
||||
a("g", "v", "r", "", "name", admission.Create),
|
||||
a("2", "v", "r2", "", "name", admission.Create),
|
||||
a("2", "v", "r4", "scale", "name", admission.Create),
|
||||
),
|
||||
},
|
||||
}
|
||||
for name, tt := range table {
|
||||
for _, m := range tt.match {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if !r.resource() {
|
||||
t.Errorf("%v: expected match %#v", name, m)
|
||||
}
|
||||
}
|
||||
for _, m := range tt.noMatch {
|
||||
r := RuleMatcher{tt.rule, m}
|
||||
if r.resource() {
|
||||
t.Errorf("%v: expected no match %#v", name, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +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.
|
||||
*/
|
||||
|
||||
// Package webhook checks a webhook for configured operation admission
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
type defaultServiceResolver struct{}
|
||||
|
||||
var _ admissioninit.ServiceResolver = defaultServiceResolver{}
|
||||
|
||||
// ResolveEndpoint constructs a service URL from a given namespace and name
|
||||
// note that the name and namespace are required and by default all created addresses use HTTPS scheme.
|
||||
// for example:
|
||||
// name=ross namespace=andromeda resolves to https://ross.andromeda.svc:443
|
||||
func (sr defaultServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
|
||||
if len(name) == 0 || len(namespace) == 0 {
|
||||
return nil, errors.New("cannot resolve an empty service name or namespace")
|
||||
}
|
||||
return &url.URL{Scheme: "https", Host: fmt.Sprintf("%s.%s.svc:443", name, namespace)}, nil
|
||||
}
|
||||
@@ -1,59 +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.
|
||||
*/
|
||||
|
||||
// Package webhook checks a webhook for configured operation admission
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultServiceResolver(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
serviceName string
|
||||
serviceNamespace string
|
||||
expectedOutput string
|
||||
expectError bool
|
||||
}{
|
||||
// scenario 1: a service name along with a namespace resolves
|
||||
{serviceName: "ross", serviceNamespace: "andromeda", expectedOutput: "https://ross.andromeda.svc:443"},
|
||||
// scenario 2: a service name without a namespace does not resolve
|
||||
{serviceName: "ross", expectError: true},
|
||||
// scenario 3: cannot resolve an empty service name
|
||||
{serviceNamespace: "andromeda", expectError: true},
|
||||
}
|
||||
|
||||
// act
|
||||
for index, scenario := range scenarios {
|
||||
t.Run(fmt.Sprintf("scenario %d", index), func(t *testing.T) {
|
||||
target := defaultServiceResolver{}
|
||||
serviceURL, err := target.ResolveEndpoint(scenario.serviceNamespace, scenario.serviceName)
|
||||
|
||||
if err != nil && !scenario.expectError {
|
||||
t.Errorf("unexpected error has occurred = %v", err)
|
||||
}
|
||||
if err == nil && scenario.expectError {
|
||||
t.Error("expected an error but got nothing")
|
||||
}
|
||||
if !scenario.expectError {
|
||||
if serviceURL.String() != scenario.expectedOutput {
|
||||
t.Errorf("expected = %s, got = %s", scenario.expectedOutput, serviceURL.String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user