mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
Merge pull request #54414 from deads2k/admission-08-options
Automatic merge from submit-queue (batch tested with PRs 53760, 48996, 51267, 54414). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. update admission webhook to handle multiple auth domains Fixes https://github.com/kubernetes/kubernetes/issues/54404 Adds some wiring to have the admission plugin accept a config file for per-apiserver configuration. @kubernetes/sig-auth-api-reviews @deads2k @ericchiang @liggitt in particular @kubernetes/sig-api-machinery-pr-reviews @lavalamp @caesarxuchao @sttts @cheftako ```release-note generic webhook admission now takes a config file which describes how to authenticate to webhook servers ```
This commit is contained in:
commit
17638ee018
@ -466,26 +466,6 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp
|
|||||||
if proxyTransport != nil && proxyTransport.Dial != nil {
|
if proxyTransport != nil && proxyTransport.Dial != nil {
|
||||||
webhookClientConfig.Dial = proxyTransport.Dial
|
webhookClientConfig.Dial = proxyTransport.Dial
|
||||||
}
|
}
|
||||||
// TODO: this is the wrong cert/key pair.
|
|
||||||
// Given the generic case of webhook admission from a generic apiserver,
|
|
||||||
// this key pair should be signed by the the API server's client CA.
|
|
||||||
// Read client cert/key for plugins that need to make calls out
|
|
||||||
certBytes, keyBytes := []byte{}, []byte{}
|
|
||||||
if len(s.ProxyClientCertFile) > 0 && len(s.ProxyClientKeyFile) > 0 {
|
|
||||||
var err error
|
|
||||||
certBytes, err = ioutil.ReadFile(s.ProxyClientCertFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, nil, fmt.Errorf("failed to read proxy client cert file from: %s, err: %v", s.ProxyClientCertFile, err)
|
|
||||||
}
|
|
||||||
keyBytes, err = ioutil.ReadFile(s.ProxyClientKeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, nil, fmt.Errorf("failed to read proxy client key file from: %s, err: %v", s.ProxyClientKeyFile, err)
|
|
||||||
}
|
|
||||||
webhookClientConfig.TLSClientConfig.CertData = certBytes
|
|
||||||
webhookClientConfig.TLSClientConfig.KeyData = keyBytes
|
|
||||||
}
|
|
||||||
webhookClientConfig.UserAgent = "kube-apiserver-admission"
|
|
||||||
webhookClientConfig.Timeout = 30 * time.Second
|
|
||||||
|
|
||||||
err = s.Admission.ApplyTo(
|
err = s.Admission.ApplyTo(
|
||||||
genericConfig,
|
genericConfig,
|
||||||
|
@ -5,6 +5,8 @@ go_library(
|
|||||||
srcs = [
|
srcs = [
|
||||||
"admission.go",
|
"admission.go",
|
||||||
"admissionreview.go",
|
"admissionreview.go",
|
||||||
|
"authentication.go",
|
||||||
|
"config.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"rules.go",
|
"rules.go",
|
||||||
"serviceresolver.go",
|
"serviceresolver.go",
|
||||||
@ -21,15 +23,17 @@ go_library(
|
|||||||
"//vendor/k8s.io/apimachinery/pkg/api/errors: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/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer: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/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/wait: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:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/configuration: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/initializer:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/kubernetes: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/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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,6 +41,7 @@ go_test(
|
|||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"admission_test.go",
|
"admission_test.go",
|
||||||
|
"authentication_test.go",
|
||||||
"certs_test.go",
|
"certs_test.go",
|
||||||
"rules_test.go",
|
"rules_test.go",
|
||||||
"serviceresolver_test.go",
|
"serviceresolver_test.go",
|
||||||
@ -49,11 +54,14 @@ go_test(
|
|||||||
"//pkg/apis/admission/install:go_default_library",
|
"//pkg/apis/admission/install:go_default_library",
|
||||||
"//vendor/k8s.io/api/admission/v1alpha1: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/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/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema: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/admission:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/authentication/user: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/rest:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@ -30,10 +31,10 @@ import (
|
|||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/configuration"
|
"k8s.io/apiserver/pkg/admission/configuration"
|
||||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||||
@ -42,17 +43,9 @@ import (
|
|||||||
admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||||
|
|
||||||
// install the clientgo admission API for use with api registry
|
// install the clientgo admission API for use with api registry
|
||||||
"path"
|
|
||||||
|
|
||||||
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
groupVersions = []schema.GroupVersion{
|
|
||||||
admissionv1alpha1.SchemeGroupVersion,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrCallingWebhook struct {
|
type ErrCallingWebhook struct {
|
||||||
WebhookName string
|
WebhookName string
|
||||||
Reason error
|
Reason error
|
||||||
@ -68,7 +61,7 @@ func (e *ErrCallingWebhook) Error() string {
|
|||||||
// Register registers a plugin
|
// Register registers a plugin
|
||||||
func Register(plugins *admission.Plugins) {
|
func Register(plugins *admission.Plugins) {
|
||||||
plugins.Register("GenericAdmissionWebhook", func(configFile io.Reader) (admission.Interface, error) {
|
plugins.Register("GenericAdmissionWebhook", func(configFile io.Reader) (admission.Interface, error) {
|
||||||
plugin, err := NewGenericAdmissionWebhook()
|
plugin, err := NewGenericAdmissionWebhook(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -84,7 +77,23 @@ type WebhookSource interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGenericAdmissionWebhook returns a generic admission webhook plugin.
|
// NewGenericAdmissionWebhook returns a generic admission webhook plugin.
|
||||||
func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) {
|
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{
|
return &GenericAdmissionWebhook{
|
||||||
Handler: admission.NewHandler(
|
Handler: admission.NewHandler(
|
||||||
admission.Connect,
|
admission.Connect,
|
||||||
@ -92,6 +101,7 @@ func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) {
|
|||||||
admission.Delete,
|
admission.Delete,
|
||||||
admission.Update,
|
admission.Update,
|
||||||
),
|
),
|
||||||
|
authInfoResolver: authInfoResolver,
|
||||||
serviceResolver: defaultServiceResolver{},
|
serviceResolver: defaultServiceResolver{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -103,7 +113,7 @@ type GenericAdmissionWebhook struct {
|
|||||||
serviceResolver admissioninit.ServiceResolver
|
serviceResolver admissioninit.ServiceResolver
|
||||||
negotiatedSerializer runtime.NegotiatedSerializer
|
negotiatedSerializer runtime.NegotiatedSerializer
|
||||||
|
|
||||||
restClientConfig *rest.Config
|
authInfoResolver AuthenticationInfoResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -112,8 +122,14 @@ var (
|
|||||||
_ = genericadmissioninit.WantsExternalKubeClientSet(&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) {
|
func (a *GenericAdmissionWebhook) SetWebhookRESTClientConfig(in *rest.Config) {
|
||||||
a.restClientConfig = in
|
if in != nil {
|
||||||
|
a.authInfoResolver = &dialOverridingAuthenticationInfoResolver{
|
||||||
|
dialFn: in.Dial,
|
||||||
|
delegate: a.authInfoResolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
||||||
@ -138,9 +154,6 @@ func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *GenericAdmissionWebhook) Validate() error {
|
func (a *GenericAdmissionWebhook) Validate() error {
|
||||||
if a.restClientConfig == nil {
|
|
||||||
return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a restClientConfig to be provided")
|
|
||||||
}
|
|
||||||
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("the GenericAdmissionWebhook admission plugin requires a Kubernetes client to be provided")
|
||||||
}
|
}
|
||||||
@ -266,16 +279,21 @@ func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.Exte
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook) (*rest.RESTClient, error) {
|
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)
|
u, err := a.serviceResolver.ResolveEndpoint(h.ClientConfig.Service.Namespace, h.ClientConfig.Service.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: cache these instead of constructing one each time
|
// TODO: cache these instead of constructing one each time
|
||||||
cfg := rest.CopyConfig(a.restClientConfig)
|
restConfig, err := a.authInfoResolver.ClientConfigFor(serverName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg := rest.CopyConfig(restConfig)
|
||||||
cfg.Host = u.Host
|
cfg.Host = u.Host
|
||||||
cfg.APIPath = path.Join(u.Path, h.ClientConfig.URLPath)
|
cfg.APIPath = path.Join(u.Path, h.ClientConfig.URLPath)
|
||||||
cfg.TLSClientConfig.ServerName = h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc"
|
cfg.TLSClientConfig.ServerName = serverName
|
||||||
cfg.TLSClientConfig.CAData = h.ClientConfig.CABundle
|
cfg.TLSClientConfig.CAData = h.ClientConfig.CABundle
|
||||||
cfg.ContentConfig.NegotiatedSerializer = a.negotiatedSerializer
|
cfg.ContentConfig.NegotiatedSerializer = a.negotiatedSerializer
|
||||||
return rest.UnversionedRESTClientFor(cfg)
|
return rest.UnversionedRESTClientFor(cfg)
|
||||||
|
@ -32,10 +32,10 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
_ "k8s.io/kubernetes/pkg/apis/admission/install"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -86,14 +86,19 @@ func TestAdmit(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("this should never happen? %v", err)
|
t.Fatalf("this should never happen? %v", err)
|
||||||
}
|
}
|
||||||
wh, err := NewGenericAdmissionWebhook()
|
wh, err := NewGenericAdmissionWebhook(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
wh.restClientConfig = &rest.Config{}
|
wh.authInfoResolver = &fakeAuthenticationInfoResolver{
|
||||||
wh.restClientConfig.TLSClientConfig.CAData = caCert
|
restConfig: &rest.Config{
|
||||||
wh.restClientConfig.TLSClientConfig.CertData = clientCert
|
TLSClientConfig: rest.TLSClientConfig{
|
||||||
wh.restClientConfig.TLSClientConfig.KeyData = clientKey
|
CAData: caCert,
|
||||||
|
CertData: clientCert,
|
||||||
|
KeyData: clientKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Set up a test object for the call
|
// Set up a test object for the call
|
||||||
kind := api.Kind("Pod").WithVersion("v1")
|
kind := api.Kind("Pod").WithVersion("v1")
|
||||||
@ -318,3 +323,11 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeAuthenticationInfoResolver struct {
|
||||||
|
restConfig *rest.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) {
|
||||||
|
return c.restConfig, nil
|
||||||
|
}
|
||||||
|
157
plugin/pkg/admission/webhook/authentication.go
Normal file
157
plugin/pkg/admission/webhook/authentication.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
130
plugin/pkg/admission/webhook/authentication_test.go
Normal file
130
plugin/pkg/admission/webhook/authentication_test.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
plugin/pkg/admission/webhook/config.go
Normal file
22
plugin/pkg/admission/webhook/config.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
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"`
|
||||||
|
}
|
@ -30,6 +30,7 @@ type pluginInitializer struct {
|
|||||||
externalInformers informers.SharedInformerFactory
|
externalInformers informers.SharedInformerFactory
|
||||||
authorizer authorizer.Authorizer
|
authorizer authorizer.Authorizer
|
||||||
// webhookRESTClientConfig provies a client used to contact webhooks
|
// webhookRESTClientConfig provies a client used to contact webhooks
|
||||||
|
// TODO clean out cruft
|
||||||
webhookRESTClientConfig *rest.Config
|
webhookRESTClientConfig *rest.Config
|
||||||
scheme *runtime.Scheme
|
scheme *runtime.Scheme
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user