176 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| 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 config
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/url"
 | |
| 
 | |
| 	lru "github.com/hashicorp/golang-lru"
 | |
| 	"k8s.io/api/admissionregistration/v1beta1"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | |
| 	webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
 | |
| 	"k8s.io/client-go/rest"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	defaultCacheSize = 200
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	ErrNeedServiceOrURL = errors.New("webhook configuration must have either service or URL")
 | |
| )
 | |
| 
 | |
| // ClientManager builds REST clients to talk to webhooks. It caches the clients
 | |
| // to avoid duplicate creation.
 | |
| type ClientManager struct {
 | |
| 	authInfoResolver     AuthenticationInfoResolver
 | |
| 	serviceResolver      ServiceResolver
 | |
| 	negotiatedSerializer runtime.NegotiatedSerializer
 | |
| 	cache                *lru.Cache
 | |
| }
 | |
| 
 | |
| // NewClientManager creates a ClientManager.
 | |
| func NewClientManager() (ClientManager, error) {
 | |
| 	cache, err := lru.New(defaultCacheSize)
 | |
| 	if err != nil {
 | |
| 		return ClientManager{}, err
 | |
| 	}
 | |
| 	return ClientManager{
 | |
| 		cache: cache,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // SetAuthenticationInfoResolverWrapper sets the
 | |
| // AuthenticationInfoResolverWrapper.
 | |
| func (cm *ClientManager) SetAuthenticationInfoResolverWrapper(wrapper AuthenticationInfoResolverWrapper) {
 | |
| 	if wrapper != nil {
 | |
| 		cm.authInfoResolver = wrapper(cm.authInfoResolver)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetAuthenticationInfoResolver sets the AuthenticationInfoResolver.
 | |
| func (cm *ClientManager) SetAuthenticationInfoResolver(resolver AuthenticationInfoResolver) {
 | |
| 	cm.authInfoResolver = resolver
 | |
| }
 | |
| 
 | |
| // SetServiceResolver sets the ServiceResolver.
 | |
| func (cm *ClientManager) SetServiceResolver(sr ServiceResolver) {
 | |
| 	if sr != nil {
 | |
| 		cm.serviceResolver = sr
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetNegotiatedSerializer sets the NegotiatedSerializer.
 | |
| func (cm *ClientManager) SetNegotiatedSerializer(n runtime.NegotiatedSerializer) {
 | |
| 	cm.negotiatedSerializer = n
 | |
| }
 | |
| 
 | |
| // Validate checks if ClientManager is properly set up.
 | |
| func (cm *ClientManager) Validate() error {
 | |
| 	var errs []error
 | |
| 	if cm.negotiatedSerializer == nil {
 | |
| 		errs = append(errs, fmt.Errorf("the ClientManager requires a negotiatedSerializer"))
 | |
| 	}
 | |
| 	if cm.serviceResolver == nil {
 | |
| 		errs = append(errs, fmt.Errorf("the ClientManager requires a serviceResolver"))
 | |
| 	}
 | |
| 	if cm.authInfoResolver == nil {
 | |
| 		errs = append(errs, fmt.Errorf("the ClientManager requires an authInfoResolver"))
 | |
| 	}
 | |
| 	return utilerrors.NewAggregate(errs)
 | |
| }
 | |
| 
 | |
| // HookClient get a RESTClient from the cache, or constructs one based on the
 | |
| // webhook configuration.
 | |
| func (cm *ClientManager) HookClient(h *v1beta1.Webhook) (*rest.RESTClient, error) {
 | |
| 	cacheKey, err := json.Marshal(h.ClientConfig)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if client, ok := cm.cache.Get(string(cacheKey)); ok {
 | |
| 		return client.(*rest.RESTClient), nil
 | |
| 	}
 | |
| 
 | |
| 	complete := func(cfg *rest.Config) (*rest.RESTClient, error) {
 | |
| 		cfg.TLSClientConfig.CAData = h.ClientConfig.CABundle
 | |
| 		cfg.ContentConfig.NegotiatedSerializer = cm.negotiatedSerializer
 | |
| 		cfg.ContentConfig.ContentType = runtime.ContentTypeJSON
 | |
| 		client, err := rest.UnversionedRESTClientFor(cfg)
 | |
| 		if err == nil {
 | |
| 			cm.cache.Add(string(cacheKey), client)
 | |
| 		}
 | |
| 		return client, err
 | |
| 	}
 | |
| 
 | |
| 	if svc := h.ClientConfig.Service; svc != nil {
 | |
| 		serverName := svc.Name + "." + svc.Namespace + ".svc"
 | |
| 		restConfig, err := cm.authInfoResolver.ClientConfigFor(serverName, false)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		cfg := rest.CopyConfig(restConfig)
 | |
| 		host := serverName + ":443"
 | |
| 		cfg.Host = "https://" + host
 | |
| 		if svc.Path != nil {
 | |
| 			cfg.APIPath = *svc.Path
 | |
| 		}
 | |
| 		cfg.TLSClientConfig.ServerName = serverName
 | |
| 
 | |
| 		delegateDialer := cfg.Dial
 | |
| 		if delegateDialer == nil {
 | |
| 			delegateDialer = net.Dial
 | |
| 		}
 | |
| 		cfg.Dial = func(network, addr string) (net.Conn, error) {
 | |
| 			if addr == host {
 | |
| 				u, err := cm.serviceResolver.ResolveEndpoint(svc.Namespace, svc.Name)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				addr = u.Host
 | |
| 			}
 | |
| 			return delegateDialer(network, addr)
 | |
| 		}
 | |
| 
 | |
| 		return complete(cfg)
 | |
| 	}
 | |
| 
 | |
| 	if h.ClientConfig.URL == nil {
 | |
| 		return nil, &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: ErrNeedServiceOrURL}
 | |
| 	}
 | |
| 
 | |
| 	u, err := url.Parse(*h.ClientConfig.URL)
 | |
| 	if err != nil {
 | |
| 		return nil, &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Unparsable URL: %v", err)}
 | |
| 	}
 | |
| 
 | |
| 	restConfig, err := cm.authInfoResolver.ClientConfigFor(u.Host, true)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	cfg := rest.CopyConfig(restConfig)
 | |
| 	cfg.Host = u.Host
 | |
| 	cfg.APIPath = u.Path
 | |
| 
 | |
| 	return complete(cfg)
 | |
| }
 |