github.com/cilium/cilium@v1.16.2/pkg/identity/identity.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package identity
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"net"
    10  	"strconv"
    11  
    12  	"github.com/cilium/cilium/pkg/labels"
    13  	"github.com/cilium/cilium/pkg/option"
    14  )
    15  
    16  const (
    17  	NodeLocalIdentityType    = "node_local"
    18  	ReservedIdentityType     = "reserved"
    19  	ClusterLocalIdentityType = "cluster_local"
    20  	WellKnownIdentityType    = "well_known"
    21  	RemoteNodeIdentityType   = "remote_node"
    22  )
    23  
    24  // Identity is the representation of the security context for a particular set of
    25  // labels.
    26  type Identity struct {
    27  	// Identity's ID.
    28  	ID NumericIdentity `json:"id"`
    29  	// Set of labels that belong to this Identity.
    30  	Labels labels.Labels `json:"labels"`
    31  
    32  	// LabelArray contains the same labels as Labels in a form of a list, used
    33  	// for faster lookup.
    34  	LabelArray labels.LabelArray `json:"-"`
    35  
    36  	// CIDRLabel is the primary identity label when the identity represents
    37  	// a CIDR. The Labels field will consist of all matching prefixes, e.g.
    38  	// 10.0.0.0/8
    39  	// 10.0.0.0/7
    40  	// 10.0.0.0/6
    41  	// [...]
    42  	// reserved:world
    43  	//
    44  	// The CIDRLabel field will only contain 10.0.0.0/8
    45  	CIDRLabel labels.Labels `json:"-"`
    46  
    47  	// ReferenceCount counts the number of references pointing to this
    48  	// identity. This field is used by the owning cache of the identity.
    49  	ReferenceCount int `json:"-"`
    50  }
    51  
    52  // IPIdentityPair is a pairing of an IP and the security identity to which that
    53  // IP corresponds. May include an optional Mask which, if present, denotes that
    54  // the IP represents a CIDR with the specified Mask.
    55  //
    56  // WARNING - STABLE API
    57  // This structure is written as JSON to the key-value store. Do NOT modify this
    58  // structure in ways which are not JSON forward compatible.
    59  type IPIdentityPair struct {
    60  	IP           net.IP          `json:"IP"`
    61  	Mask         net.IPMask      `json:"Mask"`
    62  	HostIP       net.IP          `json:"HostIP"`
    63  	ID           NumericIdentity `json:"ID"`
    64  	Key          uint8           `json:"Key"`
    65  	Metadata     string          `json:"Metadata"`
    66  	K8sNamespace string          `json:"K8sNamespace,omitempty"`
    67  	K8sPodName   string          `json:"K8sPodName,omitempty"`
    68  	NamedPorts   []NamedPort     `json:"NamedPorts,omitempty"`
    69  }
    70  
    71  type IdentityMap map[NumericIdentity]labels.LabelArray
    72  
    73  // GetKeyName returns the kvstore key to be used for the IPIdentityPair
    74  func (pair *IPIdentityPair) GetKeyName() string { return pair.PrefixString() }
    75  
    76  // Marshal returns the IPIdentityPair object as JSON byte slice
    77  func (pair *IPIdentityPair) Marshal() ([]byte, error) { return json.Marshal(pair) }
    78  
    79  // Unmarshal parses the JSON byte slice and updates the IPIdentityPair receiver
    80  func (pair *IPIdentityPair) Unmarshal(key string, data []byte) error {
    81  	newPair := IPIdentityPair{}
    82  	if err := json.Unmarshal(data, &newPair); err != nil {
    83  		return err
    84  	}
    85  
    86  	if got := newPair.GetKeyName(); got != key {
    87  		return fmt.Errorf("IP address does not match key: expected %s, got %s", key, got)
    88  	}
    89  
    90  	*pair = newPair
    91  	return nil
    92  }
    93  
    94  // NamedPort is a mapping from a port name to a port number and protocol.
    95  //
    96  // WARNING - STABLE API
    97  // This structure is written as JSON to the key-value store. Do NOT modify this
    98  // structure in ways which are not JSON forward compatible.
    99  type NamedPort struct {
   100  	Name     string `json:"Name"`
   101  	Port     uint16 `json:"Port"`
   102  	Protocol string `json:"Protocol"`
   103  }
   104  
   105  // Sanitize takes a partially initialized Identity (for example, deserialized
   106  // from json) and reconstitutes the full object from what has been restored.
   107  func (id *Identity) Sanitize() {
   108  	if id.Labels != nil {
   109  		id.LabelArray = id.Labels.LabelArray()
   110  	}
   111  }
   112  
   113  // StringID returns the identity identifier as string
   114  func (id *Identity) StringID() string {
   115  	return id.ID.StringID()
   116  }
   117  
   118  // StringID returns the identity identifier as string
   119  func (id *Identity) String() string {
   120  	return id.ID.StringID()
   121  }
   122  
   123  // IsReserved returns whether the identity represents a reserved identity
   124  // (true), or not (false).
   125  func (id *Identity) IsReserved() bool {
   126  	return LookupReservedIdentity(id.ID) != nil
   127  }
   128  
   129  // IsFixed returns whether the identity represents a fixed identity
   130  // (true), or not (false).
   131  func (id *Identity) IsFixed() bool {
   132  	return LookupReservedIdentity(id.ID) != nil &&
   133  		(id.ID == ReservedIdentityHost || id.ID == ReservedIdentityHealth ||
   134  			IsUserReservedIdentity(id.ID))
   135  }
   136  
   137  // IsWellKnown returns whether the identity represents a well known identity
   138  // (true), or not (false).
   139  func (id *Identity) IsWellKnown() bool {
   140  	return WellKnown.lookupByNumericIdentity(id.ID) != nil
   141  }
   142  
   143  // IsWellKnownIdentity returns true if the identity represents a well-known
   144  // identity, false otherwise.
   145  func IsWellKnownIdentity(id NumericIdentity) bool {
   146  	return WellKnown.lookupByNumericIdentity(id) != nil
   147  }
   148  
   149  // NewIdentityFromLabelArray creates a new identity
   150  func NewIdentityFromLabelArray(id NumericIdentity, lblArray labels.LabelArray) *Identity {
   151  	var lbls labels.Labels
   152  
   153  	if lblArray != nil {
   154  		lbls = lblArray.Labels()
   155  	}
   156  	return &Identity{ID: id, Labels: lbls, LabelArray: lblArray}
   157  }
   158  
   159  // NewIdentity creates a new identity
   160  func NewIdentity(id NumericIdentity, lbls labels.Labels) *Identity {
   161  	var lblArray labels.LabelArray
   162  
   163  	if lbls != nil {
   164  		lblArray = lbls.LabelArray()
   165  	}
   166  	return &Identity{ID: id, Labels: lbls, LabelArray: lblArray}
   167  }
   168  
   169  // IsHost determines whether the IP in the pair represents a host (true) or a
   170  // CIDR prefix (false)
   171  func (pair *IPIdentityPair) IsHost() bool {
   172  	return pair.Mask == nil
   173  }
   174  
   175  // PrefixString returns the IPIdentityPair's IP as either a host IP in the
   176  // format w.x.y.z if 'host' is true, or as a prefix in the format the w.x.y.z/N
   177  // if 'host' is false.
   178  func (pair *IPIdentityPair) PrefixString() string {
   179  	ipstr := pair.IP.String()
   180  
   181  	if pair.IsHost() {
   182  		return ipstr
   183  	}
   184  
   185  	ones, _ := pair.Mask.Size()
   186  	return ipstr + "/" + strconv.Itoa(ones)
   187  }
   188  
   189  // RequiresGlobalIdentity returns true if the label combination requires a
   190  // global identity
   191  func RequiresGlobalIdentity(lbls labels.Labels) bool {
   192  	return ScopeForLabels(lbls) == IdentityScopeGlobal
   193  }
   194  
   195  // ScopeForLabels returns the identity scope to be used for the label set.
   196  // If all labels are either CIDR or reserved, then returns the CIDR scope.
   197  // Note: This assumes the caller has already called LookupReservedIdentityByLabels;
   198  // it does not handle that case.
   199  func ScopeForLabels(lbls labels.Labels) NumericIdentity {
   200  	scope := IdentityScopeGlobal
   201  
   202  	// If this is a remote node, return the remote node scope.
   203  	// Note that this is not reachable when policy-cidr-selects-nodes is false or
   204  	// when enable-node-selector-labels is false, since
   205  	// callers will already have gotten a value from LookupReservedIdentityByLabels.
   206  	if lbls.Has(labels.LabelRemoteNode[labels.IDNameRemoteNode]) {
   207  		return IdentityScopeRemoteNode
   208  	}
   209  
   210  	for _, label := range lbls {
   211  		switch label.Source {
   212  		case labels.LabelSourceCIDR, labels.LabelSourceFQDN, labels.LabelSourceReserved:
   213  			scope = IdentityScopeLocal
   214  		default:
   215  			return IdentityScopeGlobal
   216  		}
   217  	}
   218  
   219  	return scope
   220  }
   221  
   222  // AddUserDefinedNumericIdentitySet adds all key-value pairs from the given map
   223  // to the map of user defined numeric identities and reserved identities.
   224  // The key-value pairs should map a numeric identity to a valid label.
   225  // Is not safe for concurrent use.
   226  func AddUserDefinedNumericIdentitySet(m map[string]string) error {
   227  	// Validate first
   228  	for k := range m {
   229  		ni, err := ParseNumericIdentity(k)
   230  		if err != nil {
   231  			return err
   232  		}
   233  		if !IsUserReservedIdentity(ni) {
   234  			return ErrNotUserIdentity
   235  		}
   236  	}
   237  	for k, lbl := range m {
   238  		ni, _ := ParseNumericIdentity(k)
   239  		AddUserDefinedNumericIdentity(ni, lbl)
   240  		AddReservedIdentity(ni, lbl)
   241  	}
   242  	return nil
   243  }
   244  
   245  // LookupReservedIdentityByLabels looks up a reserved identity by its labels and
   246  // returns it if found. Returns nil if not found.
   247  func LookupReservedIdentityByLabels(lbls labels.Labels) *Identity {
   248  	if identity := WellKnown.LookupByLabels(lbls); identity != nil {
   249  		return identity
   250  	}
   251  
   252  	// Check if a fixed identity exists.
   253  	if lbl, exists := lbls[labels.LabelKeyFixedIdentity]; exists {
   254  		// If the set of labels contain a fixed identity then and exists in
   255  		// the map of reserved IDs then return the identity of that reserved ID.
   256  		id := GetReservedID(lbl.Value)
   257  		if id != IdentityUnknown && IsUserReservedIdentity(id) {
   258  			return LookupReservedIdentity(id)
   259  		}
   260  		// If a fixed identity was not found then we return nil to avoid
   261  		// falling to a reserved identity.
   262  		return nil
   263  	}
   264  
   265  	// If there is no reserved label, return nil.
   266  	if !lbls.IsReserved() {
   267  		return nil
   268  	}
   269  
   270  	var nid NumericIdentity
   271  	if lbls.Has(labels.LabelHost[labels.IDNameHost]) {
   272  		nid = ReservedIdentityHost
   273  	} else if lbls.Has(labels.LabelRemoteNode[labels.IDNameRemoteNode]) {
   274  		// If selecting remote-nodes via CIDR policies is allowed, then
   275  		// they no longer have a reserved identity.
   276  		if option.Config.PolicyCIDRMatchesNodes() {
   277  			return nil
   278  		}
   279  		// If selecting remote-nodes via node labels is allowed, then
   280  		// they no longer have a reserved identity and are using
   281  		// IdentityScopeRemoteNode.
   282  		if option.Config.PerNodeLabelsEnabled() {
   283  			return nil
   284  		}
   285  		nid = ReservedIdentityRemoteNode
   286  		if lbls.Has(labels.LabelKubeAPIServer[labels.IDNameKubeAPIServer]) {
   287  			// If there's a kube-apiserver label, then we know this is
   288  			// kube-apiserver reserved ID, so change it as such.
   289  			// Only traffic from non-kube-apiserver nodes should be
   290  			// considered as remote-node.
   291  			nid = ReservedIdentityKubeAPIServer
   292  		}
   293  	}
   294  
   295  	if nid != IdentityUnknown {
   296  		return NewIdentity(nid, lbls)
   297  	}
   298  
   299  	// We have handled all the cases where multiple labels can be present.
   300  	// So, we make sure the set of labels only contains a single label and
   301  	// that label is of the reserved type. This is to prevent users from
   302  	// adding cilium-reserved labels into the workloads.
   303  	if len(lbls) != 1 {
   304  		return nil
   305  	}
   306  
   307  	nid = GetReservedID(lbls.ToSlice()[0].Key)
   308  	if nid != IdentityUnknown && !IsUserReservedIdentity(nid) {
   309  		return LookupReservedIdentity(nid)
   310  	}
   311  	return nil
   312  }
   313  
   314  // IdentityAllocationIsLocal returns true if a call to AllocateIdentity with
   315  // the given labels would not require accessing the KV store to allocate the
   316  // identity.
   317  // Currently, this function returns true only if the labels are those of a
   318  // reserved identity, i.e. if the slice contains a single reserved
   319  // "reserved:*" label.
   320  func IdentityAllocationIsLocal(lbls labels.Labels) bool {
   321  	// If there is only one label with the "reserved" source and a well-known
   322  	// key, the well-known identity for it can be allocated locally.
   323  	return LookupReservedIdentityByLabels(lbls) != nil
   324  }