github.com/imran-kn/cilium-fork@v1.6.9/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  }