...

Source file src/k8s.io/kubernetes/pkg/controller/bootstrap/bootstrapsigner.go

Documentation: k8s.io/kubernetes/pkg/controller/bootstrap

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package bootstrap
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"time"
    23  
    24  	"k8s.io/klog/v2"
    25  
    26  	"fmt"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/labels"
    32  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    33  	"k8s.io/apimachinery/pkg/util/wait"
    34  	informers "k8s.io/client-go/informers/core/v1"
    35  	clientset "k8s.io/client-go/kubernetes"
    36  	corelisters "k8s.io/client-go/listers/core/v1"
    37  	"k8s.io/client-go/tools/cache"
    38  	"k8s.io/client-go/util/workqueue"
    39  	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
    40  	jws "k8s.io/cluster-bootstrap/token/jws"
    41  	api "k8s.io/kubernetes/pkg/apis/core"
    42  )
    43  
    44  // SignerOptions contains options for the Signer
    45  type SignerOptions struct {
    46  	// ConfigMapNamespace is the namespace of the ConfigMap
    47  	ConfigMapNamespace string
    48  
    49  	// ConfigMapName is the name for the ConfigMap
    50  	ConfigMapName string
    51  
    52  	// TokenSecretNamespace string is the namespace for token Secrets.
    53  	TokenSecretNamespace string
    54  
    55  	// ConfigMapResync is the time.Duration at which to fully re-list configmaps.
    56  	// If zero, re-list will be delayed as long as possible
    57  	ConfigMapResync time.Duration
    58  
    59  	// SecretResync is the time.Duration at which to fully re-list secrets.
    60  	// If zero, re-list will be delayed as long as possible
    61  	SecretResync time.Duration
    62  }
    63  
    64  // DefaultSignerOptions returns a set of default options for creating a Signer.
    65  func DefaultSignerOptions() SignerOptions {
    66  	return SignerOptions{
    67  		ConfigMapNamespace:   api.NamespacePublic,
    68  		ConfigMapName:        bootstrapapi.ConfigMapClusterInfo,
    69  		TokenSecretNamespace: api.NamespaceSystem,
    70  	}
    71  }
    72  
    73  // Signer is a controller that signs a ConfigMap with a set of tokens.
    74  type Signer struct {
    75  	client             clientset.Interface
    76  	configMapKey       string
    77  	configMapName      string
    78  	configMapNamespace string
    79  	secretNamespace    string
    80  
    81  	// syncQueue handles synchronizing updates to the ConfigMap.  We'll only ever
    82  	// have one item (Named <ConfigMapName>) in this queue. We are using it
    83  	// serializes and collapses updates as they can come from both the ConfigMap
    84  	// and Secrets controllers.
    85  	syncQueue workqueue.RateLimitingInterface
    86  
    87  	secretLister corelisters.SecretLister
    88  	secretSynced cache.InformerSynced
    89  
    90  	configMapLister corelisters.ConfigMapLister
    91  	configMapSynced cache.InformerSynced
    92  }
    93  
    94  // NewSigner returns a new *Signer.
    95  func NewSigner(cl clientset.Interface, secrets informers.SecretInformer, configMaps informers.ConfigMapInformer, options SignerOptions) (*Signer, error) {
    96  	e := &Signer{
    97  		client:             cl,
    98  		configMapKey:       options.ConfigMapNamespace + "/" + options.ConfigMapName,
    99  		configMapName:      options.ConfigMapName,
   100  		configMapNamespace: options.ConfigMapNamespace,
   101  		secretNamespace:    options.TokenSecretNamespace,
   102  		secretLister:       secrets.Lister(),
   103  		secretSynced:       secrets.Informer().HasSynced,
   104  		configMapLister:    configMaps.Lister(),
   105  		configMapSynced:    configMaps.Informer().HasSynced,
   106  		syncQueue:          workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "bootstrap_signer_queue"),
   107  	}
   108  
   109  	configMaps.Informer().AddEventHandlerWithResyncPeriod(
   110  		cache.FilteringResourceEventHandler{
   111  			FilterFunc: func(obj interface{}) bool {
   112  				switch t := obj.(type) {
   113  				case *v1.ConfigMap:
   114  					return t.Name == options.ConfigMapName && t.Namespace == options.ConfigMapNamespace
   115  				default:
   116  					utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
   117  					return false
   118  				}
   119  			},
   120  			Handler: cache.ResourceEventHandlerFuncs{
   121  				AddFunc:    func(_ interface{}) { e.pokeConfigMapSync() },
   122  				UpdateFunc: func(_, _ interface{}) { e.pokeConfigMapSync() },
   123  			},
   124  		},
   125  		options.ConfigMapResync,
   126  	)
   127  
   128  	secrets.Informer().AddEventHandlerWithResyncPeriod(
   129  		cache.FilteringResourceEventHandler{
   130  			FilterFunc: func(obj interface{}) bool {
   131  				switch t := obj.(type) {
   132  				case *v1.Secret:
   133  					return t.Type == bootstrapapi.SecretTypeBootstrapToken && t.Namespace == e.secretNamespace
   134  				default:
   135  					utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
   136  					return false
   137  				}
   138  			},
   139  			Handler: cache.ResourceEventHandlerFuncs{
   140  				AddFunc:    func(_ interface{}) { e.pokeConfigMapSync() },
   141  				UpdateFunc: func(_, _ interface{}) { e.pokeConfigMapSync() },
   142  				DeleteFunc: func(_ interface{}) { e.pokeConfigMapSync() },
   143  			},
   144  		},
   145  		options.SecretResync,
   146  	)
   147  
   148  	return e, nil
   149  }
   150  
   151  // Run runs controller loops and returns when they are done
   152  func (e *Signer) Run(ctx context.Context) {
   153  	// Shut down queues
   154  	defer utilruntime.HandleCrash()
   155  	defer e.syncQueue.ShutDown()
   156  
   157  	if !cache.WaitForNamedCacheSync("bootstrap_signer", ctx.Done(), e.configMapSynced, e.secretSynced) {
   158  		return
   159  	}
   160  
   161  	logger := klog.FromContext(ctx)
   162  	logger.V(5).Info("Starting workers")
   163  	go wait.UntilWithContext(ctx, e.serviceConfigMapQueue, 0)
   164  	<-ctx.Done()
   165  	logger.V(1).Info("Shutting down")
   166  }
   167  
   168  func (e *Signer) pokeConfigMapSync() {
   169  	e.syncQueue.Add(e.configMapKey)
   170  }
   171  
   172  func (e *Signer) serviceConfigMapQueue(ctx context.Context) {
   173  	key, quit := e.syncQueue.Get()
   174  	if quit {
   175  		return
   176  	}
   177  	defer e.syncQueue.Done(key)
   178  
   179  	e.signConfigMap(ctx)
   180  }
   181  
   182  // signConfigMap computes the signatures on our latest cached objects and writes
   183  // back if necessary.
   184  func (e *Signer) signConfigMap(ctx context.Context) {
   185  	origCM := e.getConfigMap()
   186  
   187  	if origCM == nil {
   188  		return
   189  	}
   190  
   191  	var needUpdate = false
   192  
   193  	newCM := origCM.DeepCopy()
   194  
   195  	logger := klog.FromContext(ctx)
   196  
   197  	// First capture the config we are signing
   198  	content, ok := newCM.Data[bootstrapapi.KubeConfigKey]
   199  	if !ok {
   200  		logger.V(3).Info("No key in ConfigMap", "key", bootstrapapi.KubeConfigKey, "configMap", klog.KObj(origCM))
   201  		return
   202  	}
   203  
   204  	// Next remove and save all existing signatures
   205  	sigs := map[string]string{}
   206  	for key, value := range newCM.Data {
   207  		if strings.HasPrefix(key, bootstrapapi.JWSSignatureKeyPrefix) {
   208  			tokenID := strings.TrimPrefix(key, bootstrapapi.JWSSignatureKeyPrefix)
   209  			sigs[tokenID] = value
   210  			delete(newCM.Data, key)
   211  		}
   212  	}
   213  
   214  	// Now recompute signatures and store them on the new map
   215  	tokens := e.getTokens(ctx)
   216  	for tokenID, tokenValue := range tokens {
   217  		sig, err := jws.ComputeDetachedSignature(content, tokenID, tokenValue)
   218  		if err != nil {
   219  			utilruntime.HandleError(err)
   220  		}
   221  
   222  		// Check to see if this signature is changed or new.
   223  		oldSig, _ := sigs[tokenID]
   224  		if sig != oldSig {
   225  			needUpdate = true
   226  		}
   227  		delete(sigs, tokenID)
   228  
   229  		newCM.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenID] = sig
   230  	}
   231  
   232  	// If we have signatures left over we know that some signatures were
   233  	// removed.  We now need to update the ConfigMap
   234  	if len(sigs) != 0 {
   235  		needUpdate = true
   236  	}
   237  
   238  	if needUpdate {
   239  		e.updateConfigMap(ctx, newCM)
   240  	}
   241  }
   242  
   243  func (e *Signer) updateConfigMap(ctx context.Context, cm *v1.ConfigMap) {
   244  	_, err := e.client.CoreV1().ConfigMaps(cm.Namespace).Update(ctx, cm, metav1.UpdateOptions{})
   245  	if err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) {
   246  		klog.FromContext(ctx).V(3).Info("Error updating ConfigMap", "err", err)
   247  	}
   248  }
   249  
   250  // getConfigMap gets the ConfigMap we are interested in
   251  func (e *Signer) getConfigMap() *v1.ConfigMap {
   252  	configMap, err := e.configMapLister.ConfigMaps(e.configMapNamespace).Get(e.configMapName)
   253  
   254  	// If we can't get the configmap just return nil. The resync will eventually
   255  	// sync things up.
   256  	if err != nil {
   257  		if !apierrors.IsNotFound(err) {
   258  			utilruntime.HandleError(err)
   259  		}
   260  		return nil
   261  	}
   262  
   263  	return configMap
   264  }
   265  
   266  func (e *Signer) listSecrets() []*v1.Secret {
   267  	secrets, err := e.secretLister.Secrets(e.secretNamespace).List(labels.Everything())
   268  	if err != nil {
   269  		utilruntime.HandleError(err)
   270  		return nil
   271  	}
   272  
   273  	items := []*v1.Secret{}
   274  	for _, secret := range secrets {
   275  		if secret.Type == bootstrapapi.SecretTypeBootstrapToken {
   276  			items = append(items, secret)
   277  		}
   278  	}
   279  	return items
   280  }
   281  
   282  // getTokens returns a map of tokenID->tokenSecret. It ensures the token is
   283  // valid for signing.
   284  func (e *Signer) getTokens(ctx context.Context) map[string]string {
   285  	ret := map[string]string{}
   286  	secretObjs := e.listSecrets()
   287  	for _, secret := range secretObjs {
   288  		tokenID, tokenSecret, ok := validateSecretForSigning(ctx, secret)
   289  		if !ok {
   290  			continue
   291  		}
   292  
   293  		// Check and warn for duplicate secrets. Behavior here will be undefined.
   294  		if _, ok := ret[tokenID]; ok {
   295  			// This should never happen as we ensure a consistent secret name.
   296  			// But leave this in here just in case.
   297  			klog.FromContext(ctx).V(1).Info("Duplicate bootstrap tokens found for id, ignoring on the duplicate secret", "tokenID", tokenID, "ignoredSecret", klog.KObj(secret))
   298  			continue
   299  		}
   300  
   301  		// This secret looks good, add it to the list.
   302  		ret[tokenID] = tokenSecret
   303  	}
   304  
   305  	return ret
   306  }
   307  

View as plain text