k8s.io/kubernetes@v1.29.3/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.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 }