2017-02-09 18:53:23 -05:00
/ *
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 (
2020-02-07 21:16:47 -05:00
"context"
2017-02-09 18:53:23 -05:00
"strings"
"time"
2020-04-17 15:25:06 -04:00
"k8s.io/klog/v2"
2017-02-09 18:53:23 -05:00
2017-11-14 08:43:20 -05:00
"fmt"
2019-06-25 07:47:06 -04:00
v1 "k8s.io/api/core/v1"
2017-02-09 18:53:23 -05:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2020-02-08 12:30:21 -05:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-11-14 08:43:20 -05:00
"k8s.io/apimachinery/pkg/labels"
2017-02-09 18:53:23 -05:00
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
2017-11-14 08:43:20 -05:00
informers "k8s.io/client-go/informers/core/v1"
2017-02-09 18:53:23 -05:00
clientset "k8s.io/client-go/kubernetes"
2017-11-14 08:43:20 -05:00
corelisters "k8s.io/client-go/listers/core/v1"
2017-02-09 18:53:23 -05:00
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
2018-08-13 20:22:57 -04:00
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
2019-06-25 07:47:06 -04:00
jws "k8s.io/cluster-bootstrap/token/jws"
2017-11-08 17:34:54 -05:00
api "k8s.io/kubernetes/pkg/apis/core"
2017-02-09 18:53:23 -05:00
)
2019-02-26 14:51:44 -05:00
// SignerOptions contains options for the Signer
type SignerOptions struct {
2017-02-09 18:53:23 -05:00
// 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
2020-02-23 22:57:53 -05:00
// ConfigMapResync is the time.Duration at which to fully re-list configmaps.
2017-02-09 18:53:23 -05:00
// 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
}
2019-02-26 14:51:44 -05:00
// DefaultSignerOptions returns a set of default options for creating a Signer.
func DefaultSignerOptions ( ) SignerOptions {
return SignerOptions {
2017-02-09 18:53:23 -05:00
ConfigMapNamespace : api . NamespacePublic ,
2017-02-14 13:29:23 -05:00
ConfigMapName : bootstrapapi . ConfigMapClusterInfo ,
2017-02-09 18:53:23 -05:00
TokenSecretNamespace : api . NamespaceSystem ,
}
}
2019-02-26 14:51:44 -05:00
// Signer is a controller that signs a ConfigMap with a set of tokens.
type Signer struct {
2017-11-14 08:43:20 -05:00
client clientset . Interface
configMapKey string
configMapName string
configMapNamespace string
secretNamespace string
2017-02-09 18:53:23 -05:00
// 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.
2017-11-14 08:43:20 -05:00
syncQueue workqueue . RateLimitingInterface
secretLister corelisters . SecretLister
secretSynced cache . InformerSynced
2017-02-09 18:53:23 -05:00
2017-11-14 08:43:20 -05:00
configMapLister corelisters . ConfigMapLister
configMapSynced cache . InformerSynced
2017-02-09 18:53:23 -05:00
}
2019-02-26 14:51:44 -05:00
// NewSigner returns a new *Signer.
func NewSigner ( cl clientset . Interface , secrets informers . SecretInformer , configMaps informers . ConfigMapInformer , options SignerOptions ) ( * Signer , error ) {
e := & Signer {
2017-11-14 08:43:20 -05:00
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" ) ,
2017-02-09 18:53:23 -05:00
}
2017-11-14 08:43:20 -05:00
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
}
2017-02-09 18:53:23 -05:00
} ,
2017-11-14 08:43:20 -05:00
Handler : cache . ResourceEventHandlerFuncs {
AddFunc : func ( _ interface { } ) { e . pokeConfigMapSync ( ) } ,
UpdateFunc : func ( _ , _ interface { } ) { e . pokeConfigMapSync ( ) } ,
2017-02-09 18:53:23 -05:00
} ,
} ,
options . ConfigMapResync ,
)
2017-11-14 08:43:20 -05:00
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
}
2017-02-09 18:53:23 -05:00
} ,
2017-11-14 08:43:20 -05:00
Handler : cache . ResourceEventHandlerFuncs {
AddFunc : func ( _ interface { } ) { e . pokeConfigMapSync ( ) } ,
UpdateFunc : func ( _ , _ interface { } ) { e . pokeConfigMapSync ( ) } ,
DeleteFunc : func ( _ interface { } ) { e . pokeConfigMapSync ( ) } ,
2017-02-09 18:53:23 -05:00
} ,
} ,
options . SecretResync ,
)
2017-11-14 08:43:20 -05:00
2017-10-31 10:19:55 -04:00
return e , nil
2017-02-09 18:53:23 -05:00
}
// Run runs controller loops and returns when they are done
2021-04-22 14:20:58 -04:00
func ( e * Signer ) Run ( ctx context . Context ) {
2017-11-14 08:43:20 -05:00
// Shut down queues
defer utilruntime . HandleCrash ( )
defer e . syncQueue . ShutDown ( )
2021-04-22 14:20:58 -04:00
if ! cache . WaitForNamedCacheSync ( "bootstrap_signer" , ctx . Done ( ) , e . configMapSynced , e . secretSynced ) {
2017-11-14 08:43:20 -05:00
return
}
2022-10-31 05:38:23 -04:00
logger := klog . FromContext ( ctx )
logger . V ( 5 ) . Info ( "Starting workers" )
2021-04-22 14:20:58 -04:00
go wait . UntilWithContext ( ctx , e . serviceConfigMapQueue , 0 )
<- ctx . Done ( )
2022-10-31 05:38:23 -04:00
logger . V ( 1 ) . Info ( "Shutting down" )
2017-02-09 18:53:23 -05:00
}
2019-02-26 14:51:44 -05:00
func ( e * Signer ) pokeConfigMapSync ( ) {
2017-02-09 18:53:23 -05:00
e . syncQueue . Add ( e . configMapKey )
}
2021-04-22 14:20:58 -04:00
func ( e * Signer ) serviceConfigMapQueue ( ctx context . Context ) {
2017-02-09 18:53:23 -05:00
key , quit := e . syncQueue . Get ( )
if quit {
return
}
defer e . syncQueue . Done ( key )
2021-04-22 14:20:58 -04:00
e . signConfigMap ( ctx )
2017-02-09 18:53:23 -05:00
}
// signConfigMap computes the signatures on our latest cached objects and writes
// back if necessary.
2021-04-22 14:20:58 -04:00
func ( e * Signer ) signConfigMap ( ctx context . Context ) {
2017-02-09 18:53:23 -05:00
origCM := e . getConfigMap ( )
if origCM == nil {
return
}
var needUpdate = false
2017-08-15 08:14:21 -04:00
newCM := origCM . DeepCopy ( )
2017-02-09 18:53:23 -05:00
2022-10-31 05:38:23 -04:00
logger := klog . FromContext ( ctx )
2017-02-09 18:53:23 -05:00
// First capture the config we are signing
2017-02-14 13:29:23 -05:00
content , ok := newCM . Data [ bootstrapapi . KubeConfigKey ]
2017-02-09 18:53:23 -05:00
if ! ok {
2022-10-31 05:38:23 -04:00
logger . V ( 3 ) . Info ( "No key in ConfigMap" , "key" , bootstrapapi . KubeConfigKey , "configMap" , klog . KObj ( origCM ) )
2017-02-09 18:53:23 -05:00
return
}
// Next remove and save all existing signatures
sigs := map [ string ] string { }
for key , value := range newCM . Data {
2017-02-14 13:29:23 -05:00
if strings . HasPrefix ( key , bootstrapapi . JWSSignatureKeyPrefix ) {
tokenID := strings . TrimPrefix ( key , bootstrapapi . JWSSignatureKeyPrefix )
2017-02-09 18:53:23 -05:00
sigs [ tokenID ] = value
delete ( newCM . Data , key )
}
}
// Now recompute signatures and store them on the new map
2022-10-31 05:38:23 -04:00
tokens := e . getTokens ( ctx )
2017-02-09 18:53:23 -05:00
for tokenID , tokenValue := range tokens {
2019-06-25 07:47:06 -04:00
sig , err := jws . ComputeDetachedSignature ( content , tokenID , tokenValue )
2017-02-09 18:53:23 -05:00
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 )
2017-02-14 13:29:23 -05:00
newCM . Data [ bootstrapapi . JWSSignatureKeyPrefix + tokenID ] = sig
2017-02-09 18:53:23 -05:00
}
// 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 {
2021-04-22 14:20:58 -04:00
e . updateConfigMap ( ctx , newCM )
2017-02-09 18:53:23 -05:00
}
}
2021-04-22 14:20:58 -04:00
func ( e * Signer ) updateConfigMap ( ctx context . Context , cm * v1 . ConfigMap ) {
_ , err := e . client . CoreV1 ( ) . ConfigMaps ( cm . Namespace ) . Update ( ctx , cm , metav1 . UpdateOptions { } )
2017-02-09 18:53:23 -05:00
if err != nil && ! apierrors . IsConflict ( err ) && ! apierrors . IsNotFound ( err ) {
2022-10-31 05:38:23 -04:00
klog . FromContext ( ctx ) . V ( 3 ) . Info ( "Error updating ConfigMap" , "err" , err )
2017-02-09 18:53:23 -05:00
}
}
// getConfigMap gets the ConfigMap we are interested in
2019-02-26 14:51:44 -05:00
func ( e * Signer ) getConfigMap ( ) * v1 . ConfigMap {
2017-11-14 08:43:20 -05:00
configMap , err := e . configMapLister . ConfigMaps ( e . configMapNamespace ) . Get ( e . configMapName )
2017-02-09 18:53:23 -05:00
// If we can't get the configmap just return nil. The resync will eventually
// sync things up.
if err != nil {
2017-11-14 08:43:20 -05:00
if ! apierrors . IsNotFound ( err ) {
utilruntime . HandleError ( err )
}
2017-02-09 18:53:23 -05:00
return nil
}
2017-11-14 08:43:20 -05:00
return configMap
2017-02-09 18:53:23 -05:00
}
2019-02-26 14:51:44 -05:00
func ( e * Signer ) listSecrets ( ) [ ] * v1 . Secret {
2017-11-14 08:43:20 -05:00
secrets , err := e . secretLister . Secrets ( e . secretNamespace ) . List ( labels . Everything ( ) )
if err != nil {
utilruntime . HandleError ( err )
return nil
}
2017-02-09 18:53:23 -05:00
items := [ ] * v1 . Secret { }
2017-11-14 08:43:20 -05:00
for _ , secret := range secrets {
if secret . Type == bootstrapapi . SecretTypeBootstrapToken {
items = append ( items , secret )
}
2017-02-09 18:53:23 -05:00
}
return items
}
// getTokens returns a map of tokenID->tokenSecret. It ensures the token is
// valid for signing.
2022-10-31 05:38:23 -04:00
func ( e * Signer ) getTokens ( ctx context . Context ) map [ string ] string {
2017-02-09 18:53:23 -05:00
ret := map [ string ] string { }
secretObjs := e . listSecrets ( )
for _ , secret := range secretObjs {
2022-10-31 05:38:23 -04:00
tokenID , tokenSecret , ok := validateSecretForSigning ( ctx , secret )
2017-02-09 18:53:23 -05:00
if ! ok {
continue
}
// Check and warn for duplicate secrets. Behavior here will be undefined.
if _ , ok := ret [ tokenID ] ; ok {
2017-02-20 13:21:07 -05:00
// This should never happen as we ensure a consistent secret name.
// But leave this in here just in case.
2022-10-31 05:38:23 -04:00
klog . FromContext ( ctx ) . V ( 1 ) . Info ( "Duplicate bootstrap tokens found for id, ignoring on the duplicate secret" , "tokenID" , tokenID , "ignoredSecret" , klog . KObj ( secret ) )
2017-02-09 18:53:23 -05:00
continue
}
// This secret looks good, add it to the list.
ret [ tokenID ] = tokenSecret
}
return ret
}