github.phpd.cn/cilium/cilium@v1.6.12/pkg/k8s/identitybackend/identity.go (about) 1 // Copyright 2019-2020 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package identitybackend 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "reflect" 22 "strconv" 23 "strings" 24 25 "github.com/cilium/cilium/pkg/allocator" 26 "github.com/cilium/cilium/pkg/idpool" 27 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 28 clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned" 29 "github.com/cilium/cilium/pkg/k8s/informer" 30 "github.com/cilium/cilium/pkg/k8s/types" 31 k8sversion "github.com/cilium/cilium/pkg/k8s/version" 32 "github.com/cilium/cilium/pkg/kvstore" 33 "github.com/cilium/cilium/pkg/labels" 34 "github.com/cilium/cilium/pkg/logging" 35 "github.com/cilium/cilium/pkg/logging/logfields" 36 37 "github.com/sirupsen/logrus" 38 "k8s.io/api/core/v1" 39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 40 "k8s.io/apimachinery/pkg/fields" 41 k8sTypes "k8s.io/apimachinery/pkg/types" 42 "k8s.io/client-go/tools/cache" 43 ) 44 45 var ( 46 log = logging.DefaultLogger.WithField(logfields.LogSubsys, "crd-allocator") 47 ) 48 49 func NewCRDBackend(c CRDBackendConfiguration) (allocator.Backend, error) { 50 return &crdBackend{CRDBackendConfiguration: c}, nil 51 } 52 53 type CRDBackendConfiguration struct { 54 NodeName string 55 Store cache.Store 56 Client clientset.Interface 57 KeyType allocator.AllocatorKey 58 } 59 60 type crdBackend struct { 61 CRDBackendConfiguration 62 } 63 64 func (c *crdBackend) DeleteAllKeys() { 65 } 66 67 // sanitizeK8sLabels strips the 'k8s:' prefix in the labels generated by 68 // AllocatorKey.GetAsMap (when the key is k8s labels). In the CRD identity case 69 // we map the labels directly to the ciliumidentity CRD instance, and 70 // kubernetes does not allow ':' in the name of the label. These labels are not 71 // the canonical labels of the identity, but used to ease interaction with the 72 // CRD object. 73 func sanitizeK8sLabels(old map[string]string) (selected, skipped map[string]string) { 74 k8sPrefix := labels.LabelSourceK8s + ":" 75 skipped = make(map[string]string, len(old)) 76 selected = make(map[string]string, len(old)) 77 for k, v := range old { 78 if !strings.HasPrefix(k, k8sPrefix) { 79 skipped[k] = v 80 continue // skip non-k8s labels 81 } 82 k = strings.TrimPrefix(k, k8sPrefix) // k8s: is redundant 83 selected[k] = v 84 } 85 return selected, skipped 86 } 87 88 // AllocateID will create an identity CRD, thus creating the identity for this 89 // key-> ID mapping. 90 // Note: This does not create a reference to this node to indicate that it is 91 // using this identity. That must be done with AcquireReference. 92 // Note: the lock field is not supported with the k8s CRD allocator. 93 func (c *crdBackend) AllocateID(ctx context.Context, id idpool.ID, key allocator.AllocatorKey) error { 94 selectedLabels, skippedLabels := sanitizeK8sLabels(key.GetAsMap()) 95 log.WithField(logfields.Labels, skippedLabels).Info("Skipped non-kubernetes labels when labelling ciliumidentity. All labels will still be used in identity determination") 96 97 identity := &v2.CiliumIdentity{ 98 ObjectMeta: metav1.ObjectMeta{ 99 Name: id.String(), 100 Labels: selectedLabels, 101 }, 102 SecurityLabels: key.GetAsMap(), 103 Status: v2.IdentityStatus{ 104 Nodes: map[string]metav1.Time{ 105 c.NodeName: metav1.Now(), 106 }, 107 }, 108 } 109 110 _, err := c.Client.CiliumV2().CiliumIdentities().Create(identity) 111 return err 112 } 113 114 func (c *crdBackend) AllocateIDIfLocked(ctx context.Context, id idpool.ID, key allocator.AllocatorKey, lock kvstore.KVLocker) error { 115 return c.AllocateID(ctx, id, key) 116 } 117 118 // JSONPatch structure based on the RFC 6902 119 // Note: This mirros pkg/k8s/json_patch.go but using that directly would cause 120 // an import loop. 121 type JSONPatch struct { 122 OP string `json:"op,omitempty"` 123 Path string `json:"path,omitempty"` 124 Value interface{} `json:"value"` 125 } 126 127 // AcquireReference updates the status field of the CRD corresponding to id 128 // with this node. This marks that CRD as used by this node, and will stop it 129 // being garbage collected. 130 // Note: the lock field is not supported with the k8s CRD allocator. 131 func (c *crdBackend) AcquireReference(ctx context.Context, id idpool.ID, key allocator.AllocatorKey, lock kvstore.KVLocker) error { 132 identity, exists, err := c.getById(id) 133 if err != nil { 134 return err 135 } 136 if !exists { 137 return allocator.ErrIdentityNonExistent 138 } 139 140 capabilities := k8sversion.Capabilities() 141 identityOps := c.Client.CiliumV2().CiliumIdentities() 142 143 if capabilities.Patch { 144 var patch []byte 145 patch, err = json.Marshal([]JSONPatch{ 146 { 147 OP: "test", 148 Path: "/status", 149 Value: nil, 150 }, 151 { 152 OP: "add", 153 Path: "/status", 154 Value: v2.IdentityStatus{ 155 Nodes: map[string]metav1.Time{ 156 c.NodeName: metav1.Now(), 157 }, 158 }, 159 }, 160 }) 161 if err != nil { 162 return err 163 } 164 165 _, err = identityOps.Patch(identity.GetName(), k8sTypes.JSONPatchType, patch, "status") 166 if err != nil { 167 patch, err = json.Marshal([]JSONPatch{ 168 { 169 OP: "replace", 170 Path: "/status/nodes/" + c.NodeName, 171 Value: metav1.Now(), 172 }, 173 }) 174 if err != nil { 175 return err 176 } 177 _, err = identityOps.Patch(identity.GetName(), k8sTypes.JSONPatchType, patch, "status") 178 } 179 180 if err == nil { 181 return nil 182 } 183 log.WithError(err).Debug("Error patching status. Continuing update via UpdateStatus") 184 /* fall through and attempt UpdateStatus() or Update() */ 185 } 186 187 identityCopy := identity.DeepCopy() 188 if identityCopy.Status.Nodes == nil { 189 identityCopy.Status.Nodes = map[string]metav1.Time{ 190 c.NodeName: metav1.Now(), 191 } 192 } else { 193 identityCopy.Status.Nodes[c.NodeName] = metav1.Now() 194 } 195 196 if capabilities.UpdateStatus { 197 _, err = identityOps.UpdateStatus(identityCopy.CiliumIdentity) 198 if err == nil { 199 return nil 200 } 201 log.WithError(err).Debug("Error updating status. Continuing update via Update") 202 /* fall through and attempt Update() */ 203 } 204 205 _, err = identityOps.Update(identityCopy.CiliumIdentity) 206 return err 207 } 208 209 func (c *crdBackend) RunLocksGC(map[string]kvstore.Value) (map[string]kvstore.Value, error) { 210 return nil, nil 211 } 212 213 func (c *crdBackend) RunGC(staleKeysPrevRound map[string]uint64) (map[string]uint64, error) { 214 return nil, nil 215 } 216 217 // UpdateKey refreshes the reference that this node is using this key->ID 218 // mapping. It assumes that the identity already exists but will recreate it if 219 // reliablyMissing is true. 220 // Note: the lock field is not supported with the k8s CRD allocator. 221 func (c *crdBackend) UpdateKey(ctx context.Context, id idpool.ID, key allocator.AllocatorKey, reliablyMissing bool) error { 222 err := c.AcquireReference(ctx, id, key, nil) 223 if err == nil { 224 log.WithFields(logrus.Fields{ 225 logfields.Identity: id, 226 logfields.Labels: key, 227 }).Debug("Acquired reference for identity") 228 return nil 229 } 230 231 // The CRD (aka the master key) is missing. Try to recover by recreating it 232 // if reliablyMissing is set. 233 log.WithError(err).WithFields(logrus.Fields{ 234 logfields.Identity: id, 235 logfields.Labels: key, 236 }).Warning("Unable update CRD identity information with a reference for this node") 237 238 if reliablyMissing { 239 // Recreate a missing master key 240 if err = c.AllocateID(ctx, id, key); err != nil { 241 return fmt.Errorf("Unable recreate missing CRD identity %q->%q: %s", key, id, err) 242 } 243 } 244 245 return nil 246 } 247 248 func (c *crdBackend) UpdateKeyIfLocked(ctx context.Context, id idpool.ID, key allocator.AllocatorKey, reliablyMissing bool, lock kvstore.KVLocker) error { 249 return c.UpdateKey(ctx, id, key, reliablyMissing) 250 } 251 252 // Lock does not return a lock object. Locking is not supported with the k8s 253 // CRD allocator. It is here to meet interface requirements. 254 func (c *crdBackend) Lock(ctx context.Context, key allocator.AllocatorKey) (kvstore.KVLocker, error) { 255 return &crdLock{}, nil 256 } 257 258 type crdLock struct{} 259 260 // Unlock does not unlock a lock object. Locking is not supported with the k8s 261 // CRD allocator. It is here to meet interface requirements. 262 func (c *crdLock) Unlock() error { 263 return nil 264 } 265 266 // Comparator does nothing. Locking is not supported with the k8s 267 // CRD allocator. It is here to meet interface requirements. 268 func (c *crdLock) Comparator() interface{} { 269 return nil 270 } 271 272 // get returns the first identity found for the given set of labels as we might 273 // have duplicated entries identities for the same set of labels. 274 func (c *crdBackend) get(ctx context.Context, key allocator.AllocatorKey) *types.Identity { 275 if c.Store == nil { 276 return nil 277 } 278 279 for _, identityObject := range c.Store.List() { 280 identity, ok := identityObject.(*types.Identity) 281 if !ok { 282 return nil 283 } 284 285 if reflect.DeepEqual(identity.SecurityLabels, key.GetAsMap()) { 286 return identity 287 } 288 } 289 290 return nil 291 } 292 293 // Get returns the first ID which is allocated to a key in the identity CRDs in 294 // kubernetes. 295 // Note: the lock field is not supported with the k8s CRD allocator. 296 func (c *crdBackend) Get(ctx context.Context, key allocator.AllocatorKey) (idpool.ID, error) { 297 identity := c.get(ctx, key) 298 if identity == nil { 299 return idpool.NoID, nil 300 } 301 302 id, err := strconv.ParseUint(identity.Name, 10, 64) 303 if err != nil { 304 return idpool.NoID, fmt.Errorf("unable to parse value '%s': %s", identity.Name, err) 305 } 306 307 return idpool.ID(id), nil 308 } 309 310 func (c *crdBackend) GetIfLocked(ctx context.Context, key allocator.AllocatorKey, lock kvstore.KVLocker) (idpool.ID, error) { 311 return c.Get(ctx, key) 312 } 313 314 // getById fetches the identities from the local store. Returns a nil `err` and 315 // false `exists` if an Identity is not found for the given `id`. 316 func (c *crdBackend) getById(id idpool.ID) (idty *types.Identity, exists bool, err error) { 317 if c.Store == nil { 318 return nil, false, fmt.Errorf("store is not available yet") 319 } 320 321 identityTemplate := &types.Identity{ 322 CiliumIdentity: &v2.CiliumIdentity{ 323 ObjectMeta: metav1.ObjectMeta{ 324 Name: id.String(), 325 }, 326 }, 327 } 328 329 obj, exists, err := c.Store.Get(identityTemplate) 330 if err != nil { 331 return nil, exists, err 332 } 333 if !exists { 334 return nil, exists, nil 335 } 336 337 identity, ok := obj.(*types.Identity) 338 if !ok { 339 return nil, false, fmt.Errorf("invalid object") 340 } 341 return identity, true, nil 342 } 343 344 // GetByID returns the key associated with an ID. Returns nil if no key is 345 // associated with the ID. 346 // Note: the lock field is not supported with the k8s CRD allocator. 347 func (c *crdBackend) GetByID(id idpool.ID) (allocator.AllocatorKey, error) { 348 identity, exists, err := c.getById(id) 349 if err != nil { 350 return nil, err 351 } 352 if !exists { 353 return nil, nil 354 } 355 356 return c.KeyType.PutKeyFromMap(identity.SecurityLabels), nil 357 } 358 359 // Release dissociates this node from using the identity bound to the given ID. 360 // When an identity has no references it may be garbage collected. 361 func (c *crdBackend) Release(ctx context.Context, id idpool.ID, key allocator.AllocatorKey) (err error) { 362 identity, exists, err := c.getById(id) 363 if err != nil { 364 return err 365 } 366 if !exists || identity == nil { 367 return fmt.Errorf("unable to release identity %s: identity does not exist", key) 368 } 369 370 if _, ok := identity.Status.Nodes[c.NodeName]; !ok { 371 return fmt.Errorf("unable to release identity %s: identity is unused", key) 372 } 373 374 capabilities := k8sversion.Capabilities() 375 376 identityOps := c.Client.CiliumV2().CiliumIdentities() 377 if capabilities.Patch { 378 var patch []byte 379 patch, err = json.Marshal([]JSONPatch{ 380 { 381 OP: "delete", 382 Path: "/status/nodes/" + c.NodeName, 383 }, 384 }) 385 if err != nil { 386 return err 387 } 388 _, err = identityOps.Patch(identity.GetName(), k8sTypes.JSONPatchType, patch, "status") 389 if err == nil { 390 return nil 391 } 392 log.WithError(err).Debug("Error patching status. Continuing update via UpdateStatus") 393 /* fall through and attempt UpdateStatus() or Update() */ 394 } 395 396 identityCopy := identity.DeepCopy() 397 delete(identityCopy.Status.Nodes, c.NodeName) 398 399 if capabilities.UpdateStatus { 400 _, err = identityOps.UpdateStatus(identityCopy.CiliumIdentity) 401 if err == nil { 402 return nil 403 } 404 log.WithError(err).Debug("Error updating status. Continuing update via Update") 405 /* fall through and attempt Update() */ 406 } 407 408 _, err = identityOps.Update(identityCopy.CiliumIdentity) 409 return err 410 } 411 412 func (c *crdBackend) ListAndWatch(handler allocator.CacheMutations, stopChan chan struct{}) { 413 c.Store = cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc) 414 identityInformer := informer.NewInformerWithStore( 415 cache.NewListWatchFromClient(c.Client.CiliumV2().RESTClient(), 416 "ciliumidentities", v1.NamespaceAll, fields.Everything()), 417 &v2.CiliumIdentity{}, 418 0, 419 cache.ResourceEventHandlerFuncs{ 420 AddFunc: func(obj interface{}) { 421 if identity, ok := obj.(*types.Identity); ok { 422 if id, err := strconv.ParseUint(identity.Name, 10, 64); err == nil { 423 handler.OnAdd(idpool.ID(id), c.KeyType.PutKeyFromMap(identity.SecurityLabels)) 424 } 425 } 426 }, 427 UpdateFunc: func(oldObj, newObj interface{}) { 428 if identity, ok := newObj.(*types.Identity); ok { 429 if id, err := strconv.ParseUint(identity.Name, 10, 64); err == nil { 430 handler.OnModify(idpool.ID(id), c.KeyType.PutKeyFromMap(identity.SecurityLabels)) 431 } 432 } 433 }, 434 DeleteFunc: func(obj interface{}) { 435 // The delete event is sometimes for items with unknown state that are 436 // deleted anyway. 437 if deleteObj, isDeleteObj := obj.(cache.DeletedFinalStateUnknown); isDeleteObj { 438 obj = deleteObj.Obj 439 } 440 441 if identity, ok := obj.(*types.Identity); ok { 442 if id, err := strconv.ParseUint(identity.Name, 10, 64); err == nil { 443 handler.OnDelete(idpool.ID(id), c.KeyType.PutKeyFromMap(identity.SecurityLabels)) 444 } 445 } else { 446 log.Debugf("Ignoring unknown delete event %#v", obj) 447 } 448 }, 449 }, 450 types.ConvertToIdentity, 451 c.Store, 452 ) 453 454 go func() { 455 if ok := cache.WaitForCacheSync(stopChan, identityInformer.HasSynced); ok { 456 handler.OnListDone() 457 } 458 }() 459 460 identityInformer.Run(stopChan) 461 } 462 463 func (c *crdBackend) Status() (string, error) { 464 return "OK", nil 465 } 466 467 func (c *crdBackend) Encode(v string) string { 468 return v 469 }