mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +00:00
Merge pull request #54156 from deads2k/admission-06-restclient
Automatic merge from submit-queue. 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 accept client config Fixes https://github.com/kubernetes/kubernetes/issues/53827 This plumbs a complete client through the plugin initializer for admission webhooks. It achieves parity with our existing webhooks and provides flexibility if people want to do something special or different. Easy things are easy, hard things are possible. This does not change behavior for kube-apiserver. @kubernetes/sig-auth-api-reviews @kubernetes/sig-api-machinery-bugs
This commit is contained in:
commit
f07b359e5b
@ -70,6 +70,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
"//vendor/k8s.io/client-go/informers: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/tools/cache:go_default_library",
|
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||||
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
||||||
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1:go_default_library",
|
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1:go_default_library",
|
||||||
|
@ -84,6 +84,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/version"
|
"k8s.io/kubernetes/pkg/version"
|
||||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
|
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
|
||||||
|
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
_ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration
|
_ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration
|
||||||
_ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" // for workqueue metric registration
|
_ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" // for workqueue metric registration
|
||||||
@ -456,12 +457,15 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp
|
|||||||
client,
|
client,
|
||||||
sharedInformers,
|
sharedInformers,
|
||||||
serviceResolver,
|
serviceResolver,
|
||||||
proxyTransport,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, fmt.Errorf("failed to create admission plugin initializer: %v", err)
|
return nil, nil, nil, nil, nil, fmt.Errorf("failed to create admission plugin initializer: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webhookClientConfig := rest.AnonymousClientConfig(genericConfig.LoopbackClientConfig)
|
||||||
|
if proxyTransport != nil && proxyTransport.Dial != nil {
|
||||||
|
webhookClientConfig.Dial = proxyTransport.Dial
|
||||||
|
}
|
||||||
// TODO: this is the wrong cert/key pair.
|
// TODO: this is the wrong cert/key pair.
|
||||||
// Given the generic case of webhook admission from a generic apiserver,
|
// 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.
|
// this key pair should be signed by the the API server's client CA.
|
||||||
@ -477,14 +481,17 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp
|
|||||||
if err != nil {
|
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)
|
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,
|
||||||
versionedInformers,
|
versionedInformers,
|
||||||
certBytes,
|
|
||||||
keyBytes,
|
|
||||||
kubeClientConfig,
|
kubeClientConfig,
|
||||||
|
webhookClientConfig,
|
||||||
legacyscheme.Scheme,
|
legacyscheme.Scheme,
|
||||||
pluginInitializer)
|
pluginInitializer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -494,7 +501,7 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildAdmissionPluginInitializer constructs the admission plugin initializer
|
// BuildAdmissionPluginInitializer constructs the admission plugin initializer
|
||||||
func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client internalclientset.Interface, sharedInformers informers.SharedInformerFactory, serviceResolver aggregatorapiserver.ServiceResolver, proxyTransport *http.Transport) (admission.PluginInitializer, error) {
|
func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client internalclientset.Interface, sharedInformers informers.SharedInformerFactory, serviceResolver aggregatorapiserver.ServiceResolver) (admission.PluginInitializer, error) {
|
||||||
var cloudConfig []byte
|
var cloudConfig []byte
|
||||||
|
|
||||||
if s.CloudProvider.CloudConfigFile != "" {
|
if s.CloudProvider.CloudConfigFile != "" {
|
||||||
@ -515,7 +522,6 @@ func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client interna
|
|||||||
pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaRegistry)
|
pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaRegistry)
|
||||||
|
|
||||||
pluginInitializer = pluginInitializer.SetServiceResolver(serviceResolver)
|
pluginInitializer = pluginInitializer.SetServiceResolver(serviceResolver)
|
||||||
pluginInitializer = pluginInitializer.SetProxyTransport(proxyTransport)
|
|
||||||
|
|
||||||
return pluginInitializer, nil
|
return pluginInitializer, nil
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
"//vendor/k8s.io/client-go/informers: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/kube-openapi/pkg/common:go_default_library",
|
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -42,6 +42,7 @@ import (
|
|||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
clientgoinformers "k8s.io/client-go/informers"
|
clientgoinformers "k8s.io/client-go/informers"
|
||||||
clientgoclientset "k8s.io/client-go/kubernetes"
|
clientgoclientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
openapicommon "k8s.io/kube-openapi/pkg/common"
|
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||||
federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||||
"k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options"
|
"k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options"
|
||||||
@ -215,9 +216,8 @@ func NonBlockingRun(s *options.ServerRunOptions, stopCh <-chan struct{}) error {
|
|||||||
err = s.Admission.ApplyTo(
|
err = s.Admission.ApplyTo(
|
||||||
genericConfig,
|
genericConfig,
|
||||||
versionedInformers,
|
versionedInformers,
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
kubeClientConfig,
|
kubeClientConfig,
|
||||||
|
rest.AnonymousClientConfig(kubeClientConfig),
|
||||||
legacyscheme.Scheme,
|
legacyscheme.Scheme,
|
||||||
pluginInitializer,
|
pluginInitializer,
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package admission
|
package admission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
@ -71,12 +70,6 @@ type ServiceResolver interface {
|
|||||||
ResolveEndpoint(namespace, name string) (*url.URL, error)
|
ResolveEndpoint(namespace, name string) (*url.URL, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WantsProxyTransport defines a fuction that accepts a proxy transport for admission
|
|
||||||
// plugins that need to make calls to pods.
|
|
||||||
type WantsProxyTransport interface {
|
|
||||||
SetProxyTransport(proxyTransport *http.Transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PluginInitializer struct {
|
type PluginInitializer struct {
|
||||||
internalClient internalclientset.Interface
|
internalClient internalclientset.Interface
|
||||||
externalClient clientset.Interface
|
externalClient clientset.Interface
|
||||||
@ -86,9 +79,6 @@ type PluginInitializer struct {
|
|||||||
restMapper meta.RESTMapper
|
restMapper meta.RESTMapper
|
||||||
quotaRegistry quota.Registry
|
quotaRegistry quota.Registry
|
||||||
serviceResolver ServiceResolver
|
serviceResolver ServiceResolver
|
||||||
|
|
||||||
// for proving we are apiserver in call-outs
|
|
||||||
proxyTransport *http.Transport
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ admission.PluginInitializer = &PluginInitializer{}
|
var _ admission.PluginInitializer = &PluginInitializer{}
|
||||||
@ -118,12 +108,6 @@ func (i *PluginInitializer) SetServiceResolver(s ServiceResolver) *PluginInitial
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetProxyTransport sets the proxyTransport which is needed by some plugins.
|
|
||||||
func (i *PluginInitializer) SetProxyTransport(proxyTransport *http.Transport) *PluginInitializer {
|
|
||||||
i.proxyTransport = proxyTransport
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize checks the initialization interfaces implemented by each plugin
|
// Initialize checks the initialization interfaces implemented by each plugin
|
||||||
// and provide the appropriate initialization data
|
// and provide the appropriate initialization data
|
||||||
func (i *PluginInitializer) Initialize(plugin admission.Interface) {
|
func (i *PluginInitializer) Initialize(plugin admission.Interface) {
|
||||||
@ -150,8 +134,4 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
|
|||||||
if wants, ok := plugin.(WantsServiceResolver); ok {
|
if wants, ok := plugin.(WantsServiceResolver); ok {
|
||||||
wants.SetServiceResolver(i.serviceResolver)
|
wants.SetServiceResolver(i.serviceResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
if wants, ok := plugin.(WantsProxyTransport); ok {
|
|
||||||
wants.SetProxyTransport(i.proxyTransport)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) {
|
|||||||
whiteList: whiteList,
|
whiteList: whiteList,
|
||||||
}
|
}
|
||||||
|
|
||||||
genericPluginInitializer, err := initializer.New(nil, nil, fakeAuthorizer{}, nil, nil, nil)
|
genericPluginInitializer, err := initializer.New(nil, nil, fakeAuthorizer{}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ go_test(
|
|||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema: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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,10 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
@ -105,19 +102,18 @@ type GenericAdmissionWebhook struct {
|
|||||||
hookSource WebhookSource
|
hookSource WebhookSource
|
||||||
serviceResolver admissioninit.ServiceResolver
|
serviceResolver admissioninit.ServiceResolver
|
||||||
negotiatedSerializer runtime.NegotiatedSerializer
|
negotiatedSerializer runtime.NegotiatedSerializer
|
||||||
clientCert []byte
|
|
||||||
clientKey []byte
|
restClientConfig *rest.Config
|
||||||
proxyTransport *http.Transport
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ = admissioninit.WantsServiceResolver(&GenericAdmissionWebhook{})
|
_ = admissioninit.WantsServiceResolver(&GenericAdmissionWebhook{})
|
||||||
_ = genericadmissioninit.WantsClientCert(&GenericAdmissionWebhook{})
|
_ = genericadmissioninit.WantsWebhookRESTClientConfig(&GenericAdmissionWebhook{})
|
||||||
_ = genericadmissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{})
|
_ = genericadmissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{})
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *GenericAdmissionWebhook) SetProxyTransport(pt *http.Transport) {
|
func (a *GenericAdmissionWebhook) SetWebhookRESTClientConfig(in *rest.Config) {
|
||||||
a.proxyTransport = pt
|
a.restClientConfig = in
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
||||||
@ -137,18 +133,13 @@ func (a *GenericAdmissionWebhook) SetScheme(scheme *runtime.Scheme) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *GenericAdmissionWebhook) SetClientCert(cert, key []byte) {
|
|
||||||
a.clientCert = cert
|
|
||||||
a.clientKey = key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Interface) {
|
func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Interface) {
|
||||||
a.hookSource = configuration.NewExternalAdmissionHookConfigurationManager(client.Admissionregistration().ExternalAdmissionHookConfigurations())
|
a.hookSource = configuration.NewExternalAdmissionHookConfigurationManager(client.Admissionregistration().ExternalAdmissionHookConfigurations())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *GenericAdmissionWebhook) Validate() error {
|
func (a *GenericAdmissionWebhook) Validate() error {
|
||||||
if a.clientCert == nil || a.clientKey == nil {
|
if a.restClientConfig == nil {
|
||||||
return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a client certificate and the private key to be provided")
|
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")
|
||||||
@ -280,27 +271,12 @@ func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var dial func(network, addr string) (net.Conn, error)
|
|
||||||
if a.proxyTransport != nil && a.proxyTransport.Dial != nil {
|
|
||||||
dial = a.proxyTransport.Dial
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: cache these instead of constructing one each time
|
// TODO: cache these instead of constructing one each time
|
||||||
cfg := &rest.Config{
|
cfg := rest.CopyConfig(a.restClientConfig)
|
||||||
Host: u.Host,
|
cfg.Host = u.Host
|
||||||
APIPath: path.Join(u.Path, h.ClientConfig.URLPath),
|
cfg.APIPath = path.Join(u.Path, h.ClientConfig.URLPath)
|
||||||
TLSClientConfig: rest.TLSClientConfig{
|
cfg.TLSClientConfig.ServerName = h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc"
|
||||||
ServerName: h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc",
|
cfg.TLSClientConfig.CAData = h.ClientConfig.CABundle
|
||||||
CAData: h.ClientConfig.CABundle,
|
cfg.ContentConfig.NegotiatedSerializer = a.negotiatedSerializer
|
||||||
CertData: a.clientCert,
|
|
||||||
KeyData: a.clientKey,
|
|
||||||
},
|
|
||||||
UserAgent: "kube-apiserver-admission",
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
ContentConfig: rest.ContentConfig{
|
|
||||||
NegotiatedSerializer: a.negotiatedSerializer,
|
|
||||||
},
|
|
||||||
Dial: dial,
|
|
||||||
}
|
|
||||||
return rest.UnversionedRESTClientFor(cfg)
|
return rest.UnversionedRESTClientFor(cfg)
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ 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"
|
||||||
|
|
||||||
@ -89,8 +90,10 @@ func TestAdmit(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
wh.clientCert = clientCert
|
wh.restClientConfig = &rest.Config{}
|
||||||
wh.clientKey = clientKey
|
wh.restClientConfig.TLSClientConfig.CAData = caCert
|
||||||
|
wh.restClientConfig.TLSClientConfig.CertData = clientCert
|
||||||
|
wh.restClientConfig.TLSClientConfig.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")
|
||||||
|
@ -19,6 +19,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
"//vendor/k8s.io/client-go/informers: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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,17 +22,16 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pluginInitializer struct {
|
type pluginInitializer struct {
|
||||||
externalClient kubernetes.Interface
|
externalClient kubernetes.Interface
|
||||||
externalInformers informers.SharedInformerFactory
|
externalInformers informers.SharedInformerFactory
|
||||||
authorizer authorizer.Authorizer
|
authorizer authorizer.Authorizer
|
||||||
// serverIdentifyingClientCert used to provide identity when calling out to admission plugins
|
// webhookRESTClientConfig provies a client used to contact webhooks
|
||||||
serverIdentifyingClientCert []byte
|
webhookRESTClientConfig *rest.Config
|
||||||
// serverIdentifyingClientKey private key for the client certificate used when calling out to admission plugins
|
scheme *runtime.Scheme
|
||||||
serverIdentifyingClientKey []byte
|
|
||||||
scheme *runtime.Scheme
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an instance of admission plugins initializer.
|
// New creates an instance of admission plugins initializer.
|
||||||
@ -41,17 +40,15 @@ func New(
|
|||||||
extClientset kubernetes.Interface,
|
extClientset kubernetes.Interface,
|
||||||
extInformers informers.SharedInformerFactory,
|
extInformers informers.SharedInformerFactory,
|
||||||
authz authorizer.Authorizer,
|
authz authorizer.Authorizer,
|
||||||
serverIdentifyingClientCert,
|
webhookRESTClientConfig *rest.Config,
|
||||||
serverIdentifyingClientKey []byte,
|
|
||||||
scheme *runtime.Scheme,
|
scheme *runtime.Scheme,
|
||||||
) (pluginInitializer, error) {
|
) (pluginInitializer, error) {
|
||||||
return pluginInitializer{
|
return pluginInitializer{
|
||||||
externalClient: extClientset,
|
externalClient: extClientset,
|
||||||
externalInformers: extInformers,
|
externalInformers: extInformers,
|
||||||
authorizer: authz,
|
authorizer: authz,
|
||||||
serverIdentifyingClientCert: serverIdentifyingClientCert,
|
webhookRESTClientConfig: webhookRESTClientConfig,
|
||||||
serverIdentifyingClientKey: serverIdentifyingClientKey,
|
scheme: scheme,
|
||||||
scheme: scheme,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +67,8 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) {
|
|||||||
wants.SetAuthorizer(i.authorizer)
|
wants.SetAuthorizer(i.authorizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if wants, ok := plugin.(WantsClientCert); ok {
|
if wants, ok := plugin.(WantsWebhookRESTClientConfig); ok {
|
||||||
wants.SetClientCert(i.serverIdentifyingClientCert, i.serverIdentifyingClientKey)
|
wants.SetWebhookRESTClientConfig(i.webhookRESTClientConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
if wants, ok := plugin.(WantsScheme); ok {
|
if wants, ok := plugin.(WantsScheme); ok {
|
||||||
|
@ -33,7 +33,7 @@ import (
|
|||||||
// the WantsScheme interface is implemented by a plugin.
|
// the WantsScheme interface is implemented by a plugin.
|
||||||
func TestWantsScheme(t *testing.T) {
|
func TestWantsScheme(t *testing.T) {
|
||||||
scheme := runtime.NewScheme()
|
scheme := runtime.NewScheme()
|
||||||
target, err := initializer.New(nil, nil, nil, nil, nil, scheme)
|
target, err := initializer.New(nil, nil, nil, nil, scheme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ func TestWantsScheme(t *testing.T) {
|
|||||||
// TestWantsAuthorizer ensures that the authorizer is injected
|
// TestWantsAuthorizer ensures that the authorizer is injected
|
||||||
// when the WantsAuthorizer interface is implemented by a plugin.
|
// when the WantsAuthorizer interface is implemented by a plugin.
|
||||||
func TestWantsAuthorizer(t *testing.T) {
|
func TestWantsAuthorizer(t *testing.T) {
|
||||||
target, err := initializer.New(nil, nil, &TestAuthorizer{}, nil, nil, nil)
|
target, err := initializer.New(nil, nil, &TestAuthorizer{}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ func TestWantsAuthorizer(t *testing.T) {
|
|||||||
// when the WantsExternalKubeClientSet interface is implemented by a plugin.
|
// when the WantsExternalKubeClientSet interface is implemented by a plugin.
|
||||||
func TestWantsExternalKubeClientSet(t *testing.T) {
|
func TestWantsExternalKubeClientSet(t *testing.T) {
|
||||||
cs := &fake.Clientset{}
|
cs := &fake.Clientset{}
|
||||||
target, err := initializer.New(cs, nil, &TestAuthorizer{}, nil, nil, nil)
|
target, err := initializer.New(cs, nil, &TestAuthorizer{}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ func TestWantsExternalKubeClientSet(t *testing.T) {
|
|||||||
func TestWantsExternalKubeInformerFactory(t *testing.T) {
|
func TestWantsExternalKubeInformerFactory(t *testing.T) {
|
||||||
cs := &fake.Clientset{}
|
cs := &fake.Clientset{}
|
||||||
sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second)
|
sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second)
|
||||||
target, err := initializer.New(cs, sf, &TestAuthorizer{}, nil, nil, nil)
|
target, err := initializer.New(cs, sf, &TestAuthorizer{}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -89,20 +89,6 @@ func TestWantsExternalKubeInformerFactory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWantsClientCert ensures that the client certificate and key are injected
|
|
||||||
// when the WantsClientCert interface is implemented by a plugin.
|
|
||||||
func TestWantsClientCert(t *testing.T) {
|
|
||||||
target, err := initializer.New(nil, nil, nil, []byte("cert"), []byte("key"), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wantClientCert := &clientCertWanter{}
|
|
||||||
target.Initialize(wantClientCert)
|
|
||||||
if string(wantClientCert.gotCert) != "cert" || string(wantClientCert.gotKey) != "key" {
|
|
||||||
t.Errorf("expected client cert to be initialized, clientCert = %v, clientKey = %v", wantClientCert.gotCert, wantClientCert.gotKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WantExternalKubeInformerFactory is a test stub that fulfills the WantsExternalKubeInformerFactory interface
|
// WantExternalKubeInformerFactory is a test stub that fulfills the WantsExternalKubeInformerFactory interface
|
||||||
type WantExternalKubeInformerFactory struct {
|
type WantExternalKubeInformerFactory struct {
|
||||||
sf informers.SharedInformerFactory
|
sf informers.SharedInformerFactory
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
|
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
|
||||||
@ -42,10 +43,10 @@ type WantsAuthorizer interface {
|
|||||||
admission.Validator
|
admission.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
// WantsClientCert defines a fuction that accepts a cert & key for admission
|
// WantsWebhookRESTClientConfig defines a function that accepts client config for admission
|
||||||
// plugins that need to make calls and prove their identity.
|
// plugins that need to make calls and prove their identity.
|
||||||
type WantsClientCert interface {
|
type WantsWebhookRESTClientConfig interface {
|
||||||
SetClientCert(cert, key []byte)
|
SetWebhookRESTClientConfig(*rest.Config)
|
||||||
admission.Validator
|
admission.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, f, err
|
return nil, f, err
|
||||||
}
|
}
|
||||||
pluginInitializer, err := kubeadmission.New(c, f, nil, nil, nil, nil)
|
pluginInitializer, err := kubeadmission.New(c, f, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handler, f, err
|
return handler, f, err
|
||||||
}
|
}
|
||||||
|
@ -80,9 +80,8 @@ func (a *AdmissionOptions) AddFlags(fs *pflag.FlagSet) {
|
|||||||
func (a *AdmissionOptions) ApplyTo(
|
func (a *AdmissionOptions) ApplyTo(
|
||||||
c *server.Config,
|
c *server.Config,
|
||||||
informers informers.SharedInformerFactory,
|
informers informers.SharedInformerFactory,
|
||||||
serverIdentifyingClientCert []byte,
|
kubeAPIServerClientConfig *rest.Config,
|
||||||
serverIdentifyingClientKey []byte,
|
webhookClientConfig *rest.Config,
|
||||||
clientConfig *rest.Config,
|
|
||||||
scheme *runtime.Scheme,
|
scheme *runtime.Scheme,
|
||||||
pluginInitializers ...admission.PluginInitializer,
|
pluginInitializers ...admission.PluginInitializer,
|
||||||
) error {
|
) error {
|
||||||
@ -96,11 +95,11 @@ func (a *AdmissionOptions) ApplyTo(
|
|||||||
return fmt.Errorf("failed to read plugin config: %v", err)
|
return fmt.Errorf("failed to read plugin config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
clientset, err := kubernetes.NewForConfig(clientConfig)
|
clientset, err := kubernetes.NewForConfig(kubeAPIServerClientConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
genericInitializer, err := initializer.New(clientset, informers, c.Authorizer, serverIdentifyingClientCert, serverIdentifyingClientKey, scheme)
|
genericInitializer, err := initializer.New(clientset, informers, c.Authorizer, webhookClientConfig, scheme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -420,5 +420,45 @@ func AnonymousClientConfig(config *Config) *Config {
|
|||||||
QPS: config.QPS,
|
QPS: config.QPS,
|
||||||
Burst: config.Burst,
|
Burst: config.Burst,
|
||||||
Timeout: config.Timeout,
|
Timeout: config.Timeout,
|
||||||
|
Dial: config.Dial,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyConfig returns a copy of the given config
|
||||||
|
func CopyConfig(config *Config) *Config {
|
||||||
|
return &Config{
|
||||||
|
Host: config.Host,
|
||||||
|
APIPath: config.APIPath,
|
||||||
|
Prefix: config.Prefix,
|
||||||
|
ContentConfig: config.ContentConfig,
|
||||||
|
Username: config.Username,
|
||||||
|
Password: config.Password,
|
||||||
|
BearerToken: config.BearerToken,
|
||||||
|
CacheDir: config.CacheDir,
|
||||||
|
Impersonate: ImpersonationConfig{
|
||||||
|
Groups: config.Impersonate.Groups,
|
||||||
|
Extra: config.Impersonate.Extra,
|
||||||
|
UserName: config.Impersonate.UserName,
|
||||||
|
},
|
||||||
|
AuthProvider: config.AuthProvider,
|
||||||
|
AuthConfigPersister: config.AuthConfigPersister,
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
|
Insecure: config.TLSClientConfig.Insecure,
|
||||||
|
ServerName: config.TLSClientConfig.ServerName,
|
||||||
|
CertFile: config.TLSClientConfig.CertFile,
|
||||||
|
KeyFile: config.TLSClientConfig.KeyFile,
|
||||||
|
CAFile: config.TLSClientConfig.CAFile,
|
||||||
|
CertData: config.TLSClientConfig.CertData,
|
||||||
|
KeyData: config.TLSClientConfig.KeyData,
|
||||||
|
CAData: config.TLSClientConfig.CAData,
|
||||||
|
},
|
||||||
|
UserAgent: config.UserAgent,
|
||||||
|
Transport: config.Transport,
|
||||||
|
WrapTransport: config.WrapTransport,
|
||||||
|
QPS: config.QPS,
|
||||||
|
Burst: config.Burst,
|
||||||
|
RateLimiter: config.RateLimiter,
|
||||||
|
Timeout: config.Timeout,
|
||||||
|
Dial: config.Dial,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,8 @@ import (
|
|||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
"k8s.io/client-go/util/flowcontrol"
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -206,6 +208,19 @@ func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder,
|
|||||||
return &fakeCodec{}
|
return &fakeCodec{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fakeDialFunc = func(network, addr string) (net.Conn, error) {
|
||||||
|
return nil, fakeDialerError
|
||||||
|
}
|
||||||
|
var fakeDialerError = errors.New("fakedialer")
|
||||||
|
|
||||||
|
type fakeAuthProviderConfigPersister struct{}
|
||||||
|
|
||||||
|
func (fakeAuthProviderConfigPersister) Persist(map[string]string) error {
|
||||||
|
return fakeAuthProviderConfigPersisterError
|
||||||
|
}
|
||||||
|
|
||||||
|
var fakeAuthProviderConfigPersisterError = errors.New("fakeAuthProviderConfigPersisterError")
|
||||||
|
|
||||||
func TestAnonymousConfig(t *testing.T) {
|
func TestAnonymousConfig(t *testing.T) {
|
||||||
f := fuzz.New().NilChance(0.0).NumElements(1, 1)
|
f := fuzz.New().NilChance(0.0).NumElements(1, 1)
|
||||||
f.Funcs(
|
f.Funcs(
|
||||||
@ -268,9 +283,94 @@ func TestAnonymousConfig(t *testing.T) {
|
|||||||
actual.WrapTransport = nil
|
actual.WrapTransport = nil
|
||||||
expected.WrapTransport = nil
|
expected.WrapTransport = nil
|
||||||
}
|
}
|
||||||
|
if actual.Dial != nil {
|
||||||
|
_, actualError := actual.Dial("", "")
|
||||||
|
_, expectedError := actual.Dial("", "")
|
||||||
|
if !reflect.DeepEqual(expectedError, actualError) {
|
||||||
|
t.Fatalf("CopyConfig dropped the Dial field")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actual.Dial = nil
|
||||||
|
expected.Dial = nil
|
||||||
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(*actual, expected) {
|
if !reflect.DeepEqual(*actual, expected) {
|
||||||
t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not: %s", diff.ObjectGoPrintDiff(expected, actual))
|
t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not: %s", diff.ObjectGoPrintDiff(expected, actual))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCopyConfig(t *testing.T) {
|
||||||
|
f := fuzz.New().NilChance(0.0).NumElements(1, 1)
|
||||||
|
f.Funcs(
|
||||||
|
func(r *runtime.Codec, f fuzz.Continue) {
|
||||||
|
codec := &fakeCodec{}
|
||||||
|
f.Fuzz(codec)
|
||||||
|
*r = codec
|
||||||
|
},
|
||||||
|
func(r *http.RoundTripper, f fuzz.Continue) {
|
||||||
|
roundTripper := &fakeRoundTripper{}
|
||||||
|
f.Fuzz(roundTripper)
|
||||||
|
*r = roundTripper
|
||||||
|
},
|
||||||
|
func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
|
||||||
|
*fn = fakeWrapperFunc
|
||||||
|
},
|
||||||
|
func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
|
||||||
|
serializer := &fakeNegotiatedSerializer{}
|
||||||
|
f.Fuzz(serializer)
|
||||||
|
*r = serializer
|
||||||
|
},
|
||||||
|
func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
|
||||||
|
limiter := &fakeLimiter{}
|
||||||
|
f.Fuzz(limiter)
|
||||||
|
*r = limiter
|
||||||
|
},
|
||||||
|
func(r *AuthProviderConfigPersister, f fuzz.Continue) {
|
||||||
|
*r = fakeAuthProviderConfigPersister{}
|
||||||
|
},
|
||||||
|
func(r *func(network, addr string) (net.Conn, error), f fuzz.Continue) {
|
||||||
|
*r = fakeDialFunc
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
original := &Config{}
|
||||||
|
f.Fuzz(original)
|
||||||
|
actual := CopyConfig(original)
|
||||||
|
expected := *original
|
||||||
|
|
||||||
|
// this is the list of known risky fields, add to this list if a new field
|
||||||
|
// is added to Config, update CopyConfig to preserve the field otherwise.
|
||||||
|
|
||||||
|
// The DeepEqual cannot handle the func comparison, so we just verify if the
|
||||||
|
// function return the expected object.
|
||||||
|
if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) {
|
||||||
|
t.Fatalf("CopyConfig dropped the WrapTransport field")
|
||||||
|
} else {
|
||||||
|
actual.WrapTransport = nil
|
||||||
|
expected.WrapTransport = nil
|
||||||
|
}
|
||||||
|
if actual.Dial != nil {
|
||||||
|
_, actualError := actual.Dial("", "")
|
||||||
|
_, expectedError := actual.Dial("", "")
|
||||||
|
if !reflect.DeepEqual(expectedError, actualError) {
|
||||||
|
t.Fatalf("CopyConfig dropped the Dial field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual.Dial = nil
|
||||||
|
expected.Dial = nil
|
||||||
|
if actual.AuthConfigPersister != nil {
|
||||||
|
actualError := actual.AuthConfigPersister.Persist(nil)
|
||||||
|
expectedError := actual.AuthConfigPersister.Persist(nil)
|
||||||
|
if !reflect.DeepEqual(expectedError, actualError) {
|
||||||
|
t.Fatalf("CopyConfig dropped the Dial field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual.AuthConfigPersister = nil
|
||||||
|
expected.AuthConfigPersister = nil
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(*actual, expected) {
|
||||||
|
t.Fatalf("CopyConfig dropped unexpected fields, identify whether they are security related or not: %s", diff.ObjectReflectDiff(expected, *actual))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -119,7 +119,7 @@ func (o WardleServerOptions) Config() (*apiserver.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := o.Admission.ApplyTo(&serverConfig.Config, serverConfig.SharedInformerFactory, nil, nil, serverConfig.ClientConfig, apiserver.Scheme, admissionInitializer); err != nil {
|
if err := o.Admission.ApplyTo(&serverConfig.Config, serverConfig.SharedInformerFactory, serverConfig.ClientConfig, serverConfig.ClientConfig, apiserver.Scheme, admissionInitializer); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user