mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			218 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2018 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 controller
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"golang.org/x/oauth2"
 | |
| 
 | |
| 	v1authenticationapi "k8s.io/api/authentication/v1"
 | |
| 	"k8s.io/apimachinery/pkg/util/clock"
 | |
| 	"k8s.io/apimachinery/pkg/util/wait"
 | |
| 	apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
 | |
| 	clientset "k8s.io/client-go/kubernetes"
 | |
| 	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
 | |
| 	restclient "k8s.io/client-go/rest"
 | |
| 	"k8s.io/client-go/transport"
 | |
| 	"k8s.io/klog"
 | |
| 	utilpointer "k8s.io/utils/pointer"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// defaultExpirationSeconds defines the duration of a TokenRequest in seconds.
 | |
| 	defaultExpirationSeconds = int64(3600)
 | |
| 	// defaultLeewayPercent defines the percentage of expiration left before the client trigger a token rotation.
 | |
| 	// range[0, 100]
 | |
| 	defaultLeewayPercent = 20
 | |
| )
 | |
| 
 | |
| type DynamicControllerClientBuilder struct {
 | |
| 	// ClientConfig is a skeleton config to clone and use as the basis for each controller client
 | |
| 	ClientConfig *restclient.Config
 | |
| 
 | |
| 	// CoreClient is used to provision service accounts if needed and watch for their associated tokens
 | |
| 	// to construct a controller client
 | |
| 	CoreClient v1core.CoreV1Interface
 | |
| 
 | |
| 	// Namespace is the namespace used to host the service accounts that will back the
 | |
| 	// controllers.  It must be highly privileged namespace which normal users cannot inspect.
 | |
| 	Namespace string
 | |
| 
 | |
| 	// roundTripperFuncMap is a cache stores the corresponding roundtripper func for each
 | |
| 	// service account
 | |
| 	roundTripperFuncMap map[string]func(http.RoundTripper) http.RoundTripper
 | |
| 
 | |
| 	// expirationSeconds defines the token expiration seconds
 | |
| 	expirationSeconds int64
 | |
| 
 | |
| 	// leewayPercent defines the percentage of expiration left before the client trigger a token rotation.
 | |
| 	leewayPercent int
 | |
| 
 | |
| 	mutex sync.Mutex
 | |
| 
 | |
| 	clock clock.Clock
 | |
| }
 | |
| 
 | |
| func NewDynamicClientBuilder(clientConfig *restclient.Config, coreClient v1core.CoreV1Interface, ns string) ControllerClientBuilder {
 | |
| 	builder := &DynamicControllerClientBuilder{
 | |
| 		ClientConfig:        clientConfig,
 | |
| 		CoreClient:          coreClient,
 | |
| 		Namespace:           ns,
 | |
| 		roundTripperFuncMap: map[string]func(http.RoundTripper) http.RoundTripper{},
 | |
| 		expirationSeconds:   defaultExpirationSeconds,
 | |
| 		leewayPercent:       defaultLeewayPercent,
 | |
| 		clock:               clock.RealClock{},
 | |
| 	}
 | |
| 	return builder
 | |
| }
 | |
| 
 | |
| // this function only for test purpose, don't call it
 | |
| func NewTestDynamicClientBuilder(clientConfig *restclient.Config, coreClient v1core.CoreV1Interface, ns string, expirationSeconds int64, leewayPercent int) ControllerClientBuilder {
 | |
| 	builder := &DynamicControllerClientBuilder{
 | |
| 		ClientConfig:        clientConfig,
 | |
| 		CoreClient:          coreClient,
 | |
| 		Namespace:           ns,
 | |
| 		roundTripperFuncMap: map[string]func(http.RoundTripper) http.RoundTripper{},
 | |
| 		expirationSeconds:   expirationSeconds,
 | |
| 		leewayPercent:       leewayPercent,
 | |
| 		clock:               clock.RealClock{},
 | |
| 	}
 | |
| 	return builder
 | |
| }
 | |
| 
 | |
| func (t *DynamicControllerClientBuilder) Config(saName string) (*restclient.Config, error) {
 | |
| 	_, err := getOrCreateServiceAccount(t.CoreClient, t.Namespace, saName)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	configCopy := constructClient(t.Namespace, saName, t.ClientConfig)
 | |
| 
 | |
| 	t.mutex.Lock()
 | |
| 	defer t.mutex.Unlock()
 | |
| 
 | |
| 	rt, ok := t.roundTripperFuncMap[saName]
 | |
| 	if ok {
 | |
| 		configCopy.WrapTransport = rt
 | |
| 	} else {
 | |
| 		cachedTokenSource := transport.NewCachedTokenSource(&tokenSourceImpl{
 | |
| 			namespace:          t.Namespace,
 | |
| 			serviceAccountName: saName,
 | |
| 			coreClient:         t.CoreClient,
 | |
| 			expirationSeconds:  t.expirationSeconds,
 | |
| 			leewayPercent:      t.leewayPercent,
 | |
| 		})
 | |
| 		configCopy.WrapTransport = transport.TokenSourceWrapTransport(cachedTokenSource)
 | |
| 
 | |
| 		t.roundTripperFuncMap[saName] = configCopy.WrapTransport
 | |
| 	}
 | |
| 
 | |
| 	return &configCopy, nil
 | |
| }
 | |
| 
 | |
| func (t *DynamicControllerClientBuilder) ConfigOrDie(name string) *restclient.Config {
 | |
| 	clientConfig, err := t.Config(name)
 | |
| 	if err != nil {
 | |
| 		klog.Fatal(err)
 | |
| 	}
 | |
| 	return clientConfig
 | |
| }
 | |
| 
 | |
| func (t *DynamicControllerClientBuilder) Client(name string) (clientset.Interface, error) {
 | |
| 	clientConfig, err := t.Config(name)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return clientset.NewForConfig(clientConfig)
 | |
| }
 | |
| 
 | |
| func (t *DynamicControllerClientBuilder) ClientOrDie(name string) clientset.Interface {
 | |
| 	client, err := t.Client(name)
 | |
| 	if err != nil {
 | |
| 		klog.Fatal(err)
 | |
| 	}
 | |
| 	return client
 | |
| }
 | |
| 
 | |
| type tokenSourceImpl struct {
 | |
| 	namespace          string
 | |
| 	serviceAccountName string
 | |
| 	coreClient         v1core.CoreV1Interface
 | |
| 	expirationSeconds  int64
 | |
| 	leewayPercent      int
 | |
| }
 | |
| 
 | |
| func (ts *tokenSourceImpl) Token() (*oauth2.Token, error) {
 | |
| 	var retTokenRequest *v1authenticationapi.TokenRequest
 | |
| 
 | |
| 	backoff := wait.Backoff{
 | |
| 		Duration: 500 * time.Millisecond,
 | |
| 		Factor:   2, // double the timeout for every failure
 | |
| 		Steps:    4,
 | |
| 	}
 | |
| 	if err := wait.ExponentialBackoff(backoff, func() (bool, error) {
 | |
| 		if _, inErr := getOrCreateServiceAccount(ts.coreClient, ts.namespace, ts.serviceAccountName); inErr != nil {
 | |
| 			klog.Warningf("get or create service account failed: %v", inErr)
 | |
| 			return false, nil
 | |
| 		}
 | |
| 
 | |
| 		tr, inErr := ts.coreClient.ServiceAccounts(ts.namespace).CreateToken(ts.serviceAccountName, &v1authenticationapi.TokenRequest{
 | |
| 			Spec: v1authenticationapi.TokenRequestSpec{
 | |
| 				ExpirationSeconds: utilpointer.Int64Ptr(ts.expirationSeconds),
 | |
| 			},
 | |
| 		})
 | |
| 		if inErr != nil {
 | |
| 			klog.Warningf("get token failed: %v", inErr)
 | |
| 			return false, nil
 | |
| 		}
 | |
| 		retTokenRequest = tr
 | |
| 		return true, nil
 | |
| 	}); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to get token for %s/%s: %v", ts.namespace, ts.serviceAccountName, err)
 | |
| 	}
 | |
| 
 | |
| 	if retTokenRequest.Spec.ExpirationSeconds == nil {
 | |
| 		return nil, fmt.Errorf("nil pointer of expiration in token request")
 | |
| 	}
 | |
| 
 | |
| 	lifetime := retTokenRequest.Status.ExpirationTimestamp.Time.Sub(time.Now())
 | |
| 	if lifetime < time.Minute*10 {
 | |
| 		// possible clock skew issue, pin to minimum token lifetime
 | |
| 		lifetime = time.Minute * 10
 | |
| 	}
 | |
| 
 | |
| 	leeway := time.Duration(int64(lifetime) * int64(ts.leewayPercent) / 100)
 | |
| 	expiry := time.Now().Add(lifetime).Add(-1 * leeway)
 | |
| 
 | |
| 	return &oauth2.Token{
 | |
| 		AccessToken: retTokenRequest.Status.Token,
 | |
| 		TokenType:   "Bearer",
 | |
| 		Expiry:      expiry,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func constructClient(saNamespace, saName string, config *restclient.Config) restclient.Config {
 | |
| 	username := apiserverserviceaccount.MakeUsername(saNamespace, saName)
 | |
| 	ret := *restclient.AnonymousClientConfig(config)
 | |
| 	restclient.AddUserAgent(&ret, username)
 | |
| 	return ret
 | |
| }
 |