123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- /*
- Copyright 2016 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 bootstrap
- import (
- "strings"
- "time"
- "k8s.io/klog"
- "fmt"
- "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/labels"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- "k8s.io/apimachinery/pkg/util/wait"
- informers "k8s.io/client-go/informers/core/v1"
- clientset "k8s.io/client-go/kubernetes"
- corelisters "k8s.io/client-go/listers/core/v1"
- "k8s.io/client-go/tools/cache"
- "k8s.io/client-go/util/workqueue"
- bootstrapapi "k8s.io/cluster-bootstrap/token/api"
- api "k8s.io/kubernetes/pkg/apis/core"
- "k8s.io/kubernetes/pkg/controller"
- "k8s.io/kubernetes/pkg/util/metrics"
- )
- // SignerOptions contains options for the Signer
- type SignerOptions struct {
- // ConfigMapNamespace is the namespace of the ConfigMap
- ConfigMapNamespace string
- // ConfigMapName is the name for the ConfigMap
- ConfigMapName string
- // TokenSecretNamespace string is the namespace for token Secrets.
- TokenSecretNamespace string
- // ConfigMapResynce is the time.Duration at which to fully re-list configmaps.
- // If zero, re-list will be delayed as long as possible
- ConfigMapResync time.Duration
- // SecretResync is the time.Duration at which to fully re-list secrets.
- // If zero, re-list will be delayed as long as possible
- SecretResync time.Duration
- }
- // DefaultSignerOptions returns a set of default options for creating a Signer.
- func DefaultSignerOptions() SignerOptions {
- return SignerOptions{
- ConfigMapNamespace: api.NamespacePublic,
- ConfigMapName: bootstrapapi.ConfigMapClusterInfo,
- TokenSecretNamespace: api.NamespaceSystem,
- }
- }
- // Signer is a controller that signs a ConfigMap with a set of tokens.
- type Signer struct {
- client clientset.Interface
- configMapKey string
- configMapName string
- configMapNamespace string
- secretNamespace string
- // syncQueue handles synchronizing updates to the ConfigMap. We'll only ever
- // have one item (Named <ConfigMapName>) in this queue. We are using it
- // serializes and collapses updates as they can come from both the ConfigMap
- // and Secrets controllers.
- syncQueue workqueue.RateLimitingInterface
- secretLister corelisters.SecretLister
- secretSynced cache.InformerSynced
- configMapLister corelisters.ConfigMapLister
- configMapSynced cache.InformerSynced
- }
- // NewSigner returns a new *Signer.
- func NewSigner(cl clientset.Interface, secrets informers.SecretInformer, configMaps informers.ConfigMapInformer, options SignerOptions) (*Signer, error) {
- e := &Signer{
- client: cl,
- configMapKey: options.ConfigMapNamespace + "/" + options.ConfigMapName,
- configMapName: options.ConfigMapName,
- configMapNamespace: options.ConfigMapNamespace,
- secretNamespace: options.TokenSecretNamespace,
- secretLister: secrets.Lister(),
- secretSynced: secrets.Informer().HasSynced,
- configMapLister: configMaps.Lister(),
- configMapSynced: configMaps.Informer().HasSynced,
- syncQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "bootstrap_signer_queue"),
- }
- if cl.CoreV1().RESTClient().GetRateLimiter() != nil {
- if err := metrics.RegisterMetricAndTrackRateLimiterUsage("bootstrap_signer", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil {
- return nil, err
- }
- }
- configMaps.Informer().AddEventHandlerWithResyncPeriod(
- cache.FilteringResourceEventHandler{
- FilterFunc: func(obj interface{}) bool {
- switch t := obj.(type) {
- case *v1.ConfigMap:
- return t.Name == options.ConfigMapName && t.Namespace == options.ConfigMapNamespace
- default:
- utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
- return false
- }
- },
- Handler: cache.ResourceEventHandlerFuncs{
- AddFunc: func(_ interface{}) { e.pokeConfigMapSync() },
- UpdateFunc: func(_, _ interface{}) { e.pokeConfigMapSync() },
- },
- },
- options.ConfigMapResync,
- )
- secrets.Informer().AddEventHandlerWithResyncPeriod(
- cache.FilteringResourceEventHandler{
- FilterFunc: func(obj interface{}) bool {
- switch t := obj.(type) {
- case *v1.Secret:
- return t.Type == bootstrapapi.SecretTypeBootstrapToken && t.Namespace == e.secretNamespace
- default:
- utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
- return false
- }
- },
- Handler: cache.ResourceEventHandlerFuncs{
- AddFunc: func(_ interface{}) { e.pokeConfigMapSync() },
- UpdateFunc: func(_, _ interface{}) { e.pokeConfigMapSync() },
- DeleteFunc: func(_ interface{}) { e.pokeConfigMapSync() },
- },
- },
- options.SecretResync,
- )
- return e, nil
- }
- // Run runs controller loops and returns when they are done
- func (e *Signer) Run(stopCh <-chan struct{}) {
- // Shut down queues
- defer utilruntime.HandleCrash()
- defer e.syncQueue.ShutDown()
- if !controller.WaitForCacheSync("bootstrap_signer", stopCh, e.configMapSynced, e.secretSynced) {
- return
- }
- klog.V(5).Infof("Starting workers")
- go wait.Until(e.serviceConfigMapQueue, 0, stopCh)
- <-stopCh
- klog.V(1).Infof("Shutting down")
- }
- func (e *Signer) pokeConfigMapSync() {
- e.syncQueue.Add(e.configMapKey)
- }
- func (e *Signer) serviceConfigMapQueue() {
- key, quit := e.syncQueue.Get()
- if quit {
- return
- }
- defer e.syncQueue.Done(key)
- e.signConfigMap()
- }
- // signConfigMap computes the signatures on our latest cached objects and writes
- // back if necessary.
- func (e *Signer) signConfigMap() {
- origCM := e.getConfigMap()
- if origCM == nil {
- return
- }
- var needUpdate = false
- newCM := origCM.DeepCopy()
- // First capture the config we are signing
- content, ok := newCM.Data[bootstrapapi.KubeConfigKey]
- if !ok {
- klog.V(3).Infof("No %s key in %s/%s ConfigMap", bootstrapapi.KubeConfigKey, origCM.Namespace, origCM.Name)
- return
- }
- // Next remove and save all existing signatures
- sigs := map[string]string{}
- for key, value := range newCM.Data {
- if strings.HasPrefix(key, bootstrapapi.JWSSignatureKeyPrefix) {
- tokenID := strings.TrimPrefix(key, bootstrapapi.JWSSignatureKeyPrefix)
- sigs[tokenID] = value
- delete(newCM.Data, key)
- }
- }
- // Now recompute signatures and store them on the new map
- tokens := e.getTokens()
- for tokenID, tokenValue := range tokens {
- sig, err := computeDetachedSig(content, tokenID, tokenValue)
- if err != nil {
- utilruntime.HandleError(err)
- }
- // Check to see if this signature is changed or new.
- oldSig, _ := sigs[tokenID]
- if sig != oldSig {
- needUpdate = true
- }
- delete(sigs, tokenID)
- newCM.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenID] = sig
- }
- // If we have signatures left over we know that some signatures were
- // removed. We now need to update the ConfigMap
- if len(sigs) != 0 {
- needUpdate = true
- }
- if needUpdate {
- e.updateConfigMap(newCM)
- }
- }
- func (e *Signer) updateConfigMap(cm *v1.ConfigMap) {
- _, err := e.client.CoreV1().ConfigMaps(cm.Namespace).Update(cm)
- if err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) {
- klog.V(3).Infof("Error updating ConfigMap: %v", err)
- }
- }
- // getConfigMap gets the ConfigMap we are interested in
- func (e *Signer) getConfigMap() *v1.ConfigMap {
- configMap, err := e.configMapLister.ConfigMaps(e.configMapNamespace).Get(e.configMapName)
- // If we can't get the configmap just return nil. The resync will eventually
- // sync things up.
- if err != nil {
- if !apierrors.IsNotFound(err) {
- utilruntime.HandleError(err)
- }
- return nil
- }
- return configMap
- }
- func (e *Signer) listSecrets() []*v1.Secret {
- secrets, err := e.secretLister.Secrets(e.secretNamespace).List(labels.Everything())
- if err != nil {
- utilruntime.HandleError(err)
- return nil
- }
- items := []*v1.Secret{}
- for _, secret := range secrets {
- if secret.Type == bootstrapapi.SecretTypeBootstrapToken {
- items = append(items, secret)
- }
- }
- return items
- }
- // getTokens returns a map of tokenID->tokenSecret. It ensures the token is
- // valid for signing.
- func (e *Signer) getTokens() map[string]string {
- ret := map[string]string{}
- secretObjs := e.listSecrets()
- for _, secret := range secretObjs {
- tokenID, tokenSecret, ok := validateSecretForSigning(secret)
- if !ok {
- continue
- }
- // Check and warn for duplicate secrets. Behavior here will be undefined.
- if _, ok := ret[tokenID]; ok {
- // This should never happen as we ensure a consistent secret name.
- // But leave this in here just in case.
- klog.V(1).Infof("Duplicate bootstrap tokens found for id %s, ignoring on in %s/%s", tokenID, secret.Namespace, secret.Name)
- continue
- }
- // This secret looks good, add it to the list.
- ret[tokenID] = tokenSecret
- }
- return ret
- }
|