1
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
45 type SignerOptions struct {
46
47 ConfigMapNamespace string
48
49
50 ConfigMapName string
51
52
53 TokenSecretNamespace string
54
55
56
57 ConfigMapResync time.Duration
58
59
60
61 SecretResync time.Duration
62 }
63
64
65 func DefaultSignerOptions() SignerOptions {
66 return SignerOptions{
67 ConfigMapNamespace: api.NamespacePublic,
68 ConfigMapName: bootstrapapi.ConfigMapClusterInfo,
69 TokenSecretNamespace: api.NamespaceSystem,
70 }
71 }
72
73
74 type Signer struct {
75 client clientset.Interface
76 configMapKey string
77 configMapName string
78 configMapNamespace string
79 secretNamespace string
80
81
82
83
84
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
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
152 func (e *Signer) Run(ctx context.Context) {
153
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
183
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
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
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
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
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
233
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
251 func (e *Signer) getConfigMap() *v1.ConfigMap {
252 configMap, err := e.configMapLister.ConfigMaps(e.configMapNamespace).Get(e.configMapName)
253
254
255
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
283
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
294 if _, ok := ret[tokenID]; ok {
295
296
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
302 ret[tokenID] = tokenSecret
303 }
304
305 return ret
306 }
307
View as plain text