k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/bootstrap/bootstrapsigner.go (about)

     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.TypedRateLimitingInterface[string]
    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.NewTypedRateLimitingQueueWithConfig(
   107  			workqueue.DefaultTypedControllerRateLimiter[string](),
   108  			workqueue.TypedRateLimitingQueueConfig[string]{
   109  				Name: "bootstrap_signer_queue",
   110  			},
   111  		),
   112  	}
   113  
   114  	configMaps.Informer().AddEventHandlerWithResyncPeriod(
   115  		cache.FilteringResourceEventHandler{
   116  			FilterFunc: func(obj interface{}) bool {
   117  				switch t := obj.(type) {
   118  				case *v1.ConfigMap:
   119  					return t.Name == options.ConfigMapName && t.Namespace == options.ConfigMapNamespace
   120  				default:
   121  					utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
   122  					return false
   123  				}
   124  			},
   125  			Handler: cache.ResourceEventHandlerFuncs{
   126  				AddFunc:    func(_ interface{}) { e.pokeConfigMapSync() },
   127  				UpdateFunc: func(_, _ interface{}) { e.pokeConfigMapSync() },
   128  			},
   129  		},
   130  		options.ConfigMapResync,
   131  	)
   132  
   133  	secrets.Informer().AddEventHandlerWithResyncPeriod(
   134  		cache.FilteringResourceEventHandler{
   135  			FilterFunc: func(obj interface{}) bool {
   136  				switch t := obj.(type) {
   137  				case *v1.Secret:
   138  					return t.Type == bootstrapapi.SecretTypeBootstrapToken && t.Namespace == e.secretNamespace
   139  				default:
   140  					utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
   141  					return false
   142  				}
   143  			},
   144  			Handler: cache.ResourceEventHandlerFuncs{
   145  				AddFunc:    func(_ interface{}) { e.pokeConfigMapSync() },
   146  				UpdateFunc: func(_, _ interface{}) { e.pokeConfigMapSync() },
   147  				DeleteFunc: func(_ interface{}) { e.pokeConfigMapSync() },
   148  			},
   149  		},
   150  		options.SecretResync,
   151  	)
   152  
   153  	return e, nil
   154  }
   155  
   156  // Run runs controller loops and returns when they are done
   157  func (e *Signer) Run(ctx context.Context) {
   158  	// Shut down queues
   159  	defer utilruntime.HandleCrash()
   160  	defer e.syncQueue.ShutDown()
   161  
   162  	if !cache.WaitForNamedCacheSync("bootstrap_signer", ctx.Done(), e.configMapSynced, e.secretSynced) {
   163  		return
   164  	}
   165  
   166  	logger := klog.FromContext(ctx)
   167  	logger.V(5).Info("Starting workers")
   168  	go wait.UntilWithContext(ctx, e.serviceConfigMapQueue, 0)
   169  	<-ctx.Done()
   170  	logger.V(1).Info("Shutting down")
   171  }
   172  
   173  func (e *Signer) pokeConfigMapSync() {
   174  	e.syncQueue.Add(e.configMapKey)
   175  }
   176  
   177  func (e *Signer) serviceConfigMapQueue(ctx context.Context) {
   178  	key, quit := e.syncQueue.Get()
   179  	if quit {
   180  		return
   181  	}
   182  	defer e.syncQueue.Done(key)
   183  
   184  	e.signConfigMap(ctx)
   185  }
   186  
   187  // signConfigMap computes the signatures on our latest cached objects and writes
   188  // back if necessary.
   189  func (e *Signer) signConfigMap(ctx context.Context) {
   190  	origCM := e.getConfigMap()
   191  
   192  	if origCM == nil {
   193  		return
   194  	}
   195  
   196  	var needUpdate = false
   197  
   198  	newCM := origCM.DeepCopy()
   199  
   200  	logger := klog.FromContext(ctx)
   201  
   202  	// First capture the config we are signing
   203  	content, ok := newCM.Data[bootstrapapi.KubeConfigKey]
   204  	if !ok {
   205  		logger.V(3).Info("No key in ConfigMap", "key", bootstrapapi.KubeConfigKey, "configMap", klog.KObj(origCM))
   206  		return
   207  	}
   208  
   209  	// Next remove and save all existing signatures
   210  	sigs := map[string]string{}
   211  	for key, value := range newCM.Data {
   212  		if strings.HasPrefix(key, bootstrapapi.JWSSignatureKeyPrefix) {
   213  			tokenID := strings.TrimPrefix(key, bootstrapapi.JWSSignatureKeyPrefix)
   214  			sigs[tokenID] = value
   215  			delete(newCM.Data, key)
   216  		}
   217  	}
   218  
   219  	// Now recompute signatures and store them on the new map
   220  	tokens := e.getTokens(ctx)
   221  	for tokenID, tokenValue := range tokens {
   222  		sig, err := jws.ComputeDetachedSignature(content, tokenID, tokenValue)
   223  		if err != nil {
   224  			utilruntime.HandleError(err)
   225  		}
   226  
   227  		// Check to see if this signature is changed or new.
   228  		oldSig, _ := sigs[tokenID]
   229  		if sig != oldSig {
   230  			needUpdate = true
   231  		}
   232  		delete(sigs, tokenID)
   233  
   234  		newCM.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenID] = sig
   235  	}
   236  
   237  	// If we have signatures left over we know that some signatures were
   238  	// removed.  We now need to update the ConfigMap
   239  	if len(sigs) != 0 {
   240  		needUpdate = true
   241  	}
   242  
   243  	if needUpdate {
   244  		e.updateConfigMap(ctx, newCM)
   245  	}
   246  }
   247  
   248  func (e *Signer) updateConfigMap(ctx context.Context, cm *v1.ConfigMap) {
   249  	_, err := e.client.CoreV1().ConfigMaps(cm.Namespace).Update(ctx, cm, metav1.UpdateOptions{})
   250  	if err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) {
   251  		klog.FromContext(ctx).V(3).Info("Error updating ConfigMap", "err", err)
   252  	}
   253  }
   254  
   255  // getConfigMap gets the ConfigMap we are interested in
   256  func (e *Signer) getConfigMap() *v1.ConfigMap {
   257  	configMap, err := e.configMapLister.ConfigMaps(e.configMapNamespace).Get(e.configMapName)
   258  
   259  	// If we can't get the configmap just return nil. The resync will eventually
   260  	// sync things up.
   261  	if err != nil {
   262  		if !apierrors.IsNotFound(err) {
   263  			utilruntime.HandleError(err)
   264  		}
   265  		return nil
   266  	}
   267  
   268  	return configMap
   269  }
   270  
   271  func (e *Signer) listSecrets() []*v1.Secret {
   272  	secrets, err := e.secretLister.Secrets(e.secretNamespace).List(labels.Everything())
   273  	if err != nil {
   274  		utilruntime.HandleError(err)
   275  		return nil
   276  	}
   277  
   278  	items := []*v1.Secret{}
   279  	for _, secret := range secrets {
   280  		if secret.Type == bootstrapapi.SecretTypeBootstrapToken {
   281  			items = append(items, secret)
   282  		}
   283  	}
   284  	return items
   285  }
   286  
   287  // getTokens returns a map of tokenID->tokenSecret. It ensures the token is
   288  // valid for signing.
   289  func (e *Signer) getTokens(ctx context.Context) map[string]string {
   290  	ret := map[string]string{}
   291  	secretObjs := e.listSecrets()
   292  	for _, secret := range secretObjs {
   293  		tokenID, tokenSecret, ok := validateSecretForSigning(ctx, secret)
   294  		if !ok {
   295  			continue
   296  		}
   297  
   298  		// Check and warn for duplicate secrets. Behavior here will be undefined.
   299  		if _, ok := ret[tokenID]; ok {
   300  			// This should never happen as we ensure a consistent secret name.
   301  			// But leave this in here just in case.
   302  			klog.FromContext(ctx).V(1).Info("Duplicate bootstrap tokens found for id, ignoring on the duplicate secret", "tokenID", tokenID, "ignoredSecret", klog.KObj(secret))
   303  			continue
   304  		}
   305  
   306  		// This secret looks good, add it to the list.
   307  		ret[tokenID] = tokenSecret
   308  	}
   309  
   310  	return ret
   311  }