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

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package utils
     5  
     6  import (
     7  	"net"
     8  	"strings"
     9  
    10  	v1 "k8s.io/api/core/v1"
    11  	discoveryv1 "k8s.io/api/discovery/v1"
    12  	v1meta "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  
    14  	"github.com/cilium/cilium/pkg/ip"
    15  	k8sconst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
    16  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    17  	"github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels"
    18  	"github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/selection"
    19  	labelsPkg "github.com/cilium/cilium/pkg/labels"
    20  	"github.com/cilium/cilium/pkg/slices"
    21  )
    22  
    23  const (
    24  	// ServiceProxyNameLabel is the label for service proxy name in k8s service related
    25  	// objects.
    26  	serviceProxyNameLabel = "service.kubernetes.io/service-proxy-name"
    27  	// EndpointSliceMeshControllerName is a unique value used with LabelManagedBy to indicate
    28  	// the component managing an EndpointSlice.
    29  	EndpointSliceMeshControllerName = "endpointslice-mesh-controller.cilium.io"
    30  )
    31  
    32  type NamespaceNameGetter interface {
    33  	GetNamespace() string
    34  	GetName() string
    35  }
    36  
    37  // ExtractNamespace extracts the namespace of ObjectMeta.
    38  // For cluster scoped objects the Namespace field is empty and this function
    39  // assumes that the object is returned from kubernetes itself implying that
    40  // the namespace is empty only and only when the Object is cluster scoped
    41  // and thus returns empty namespace for such objects.
    42  func ExtractNamespace(np NamespaceNameGetter) string {
    43  	return np.GetNamespace()
    44  }
    45  
    46  // ExtractNamespaceOrDefault extracts the namespace of ObjectMeta, it returns default
    47  // namespace if the namespace field in the ObjectMeta is empty.
    48  func ExtractNamespaceOrDefault(np NamespaceNameGetter) string {
    49  	ns := np.GetNamespace()
    50  	if ns == "" {
    51  		return v1.NamespaceDefault
    52  	}
    53  
    54  	return ns
    55  }
    56  
    57  // GetObjNamespaceName returns the object's namespace and name.
    58  // If the object is cluster scoped then the function returns only the object name
    59  // without any namespace prefix.
    60  func GetObjNamespaceName(obj NamespaceNameGetter) string {
    61  	ns := ExtractNamespace(obj)
    62  	if ns == "" {
    63  		return obj.GetName()
    64  	}
    65  
    66  	return ns + "/" + obj.GetName()
    67  }
    68  
    69  // GetEndpointSliceListOptionsModifier returns the options modifier for endpointSlice object list.
    70  // This methods returns a ListOptions modifier which adds a label selector to
    71  // select all endpointSlice objects they are not from remote clusters in Cilium cluster mesh.
    72  // This is mostly the same behavior as kube-proxy except the cluster mesh behavior which is
    73  // tied to how Cilium internally works with clustermesh endpoints and that this function also doesn't ignore headless Services.
    74  // Given label mirroring from the service objects to endpoint slice objects were introduced in Kubernetes PR 94443,
    75  // and released as part of Kubernetes v1.20; we can start using GetServiceAndEndpointListOptionsModifier for
    76  // endpoint slices when dropping support for Kubernetes v1.19 and older. We can do that since the
    77  // serviceProxyNameLabel label will then be mirrored to endpoint slices for services with that label.
    78  // We also ignore Kubernetes endpoints coming from other clusters in the Cilium clustermesh here as
    79  // Cilium does not rely on mirrored Kubernetes EndpointSlice for any of its functionalities.
    80  func GetEndpointSliceListOptionsModifier() (func(options *v1meta.ListOptions), error) {
    81  	nonRemoteEndpointSelector, err := labels.NewRequirement(discoveryv1.LabelManagedBy, selection.NotEquals, []string{EndpointSliceMeshControllerName})
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	labelSelector := labels.NewSelector()
    87  	labelSelector = labelSelector.Add(*nonRemoteEndpointSelector)
    88  
    89  	return func(options *v1meta.ListOptions) {
    90  		options.LabelSelector = labelSelector.String()
    91  	}, nil
    92  }
    93  
    94  // GetServiceAndEndpointListOptionsModifier returns the options modifier for service and endpoint object lists.
    95  // This methods returns a ListOptions modifier which adds a label selector to only
    96  // select services that are in context of Cilium.
    97  // Unlike kube-proxy Cilium does not select services/endpoints containing k8s headless service label.
    98  // We honor service.kubernetes.io/service-proxy-name label in the service object and only
    99  // handle services that match our service proxy name. If the service proxy name for Cilium
   100  // is an empty string, we assume that Cilium is the default service handler in which case
   101  // we select all services that don't have the above mentioned label.
   102  func GetServiceAndEndpointListOptionsModifier(k8sServiceProxy string) (func(options *v1meta.ListOptions), error) {
   103  	var (
   104  		serviceNameSelector *labels.Requirement
   105  		err                 error
   106  	)
   107  
   108  	if k8sServiceProxy == "" {
   109  		serviceNameSelector, err = labels.NewRequirement(
   110  			serviceProxyNameLabel, selection.DoesNotExist, nil)
   111  	} else {
   112  		serviceNameSelector, err = labels.NewRequirement(
   113  			serviceProxyNameLabel, selection.DoubleEquals, []string{k8sServiceProxy})
   114  	}
   115  
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	labelSelector := labels.NewSelector()
   121  	labelSelector = labelSelector.Add(*serviceNameSelector)
   122  
   123  	return func(options *v1meta.ListOptions) {
   124  		options.LabelSelector = labelSelector.String()
   125  	}, nil
   126  }
   127  
   128  // GetLatestPodReadiness returns the lastest podReady condition on a given pod.
   129  func GetLatestPodReadiness(podStatus slim_corev1.PodStatus) slim_corev1.ConditionStatus {
   130  	for _, cond := range podStatus.Conditions {
   131  		if cond.Type == slim_corev1.PodReady {
   132  			return cond.Status
   133  		}
   134  	}
   135  	return slim_corev1.ConditionUnknown
   136  }
   137  
   138  // ValidIPs return a sorted slice of unique IP addresses retrieved from the given PodStatus.
   139  // Returns an error when no IPs are found.
   140  func ValidIPs(podStatus slim_corev1.PodStatus) []string {
   141  	if len(podStatus.PodIPs) == 0 && len(podStatus.PodIP) == 0 {
   142  		return nil
   143  	}
   144  
   145  	// make it a set first to avoid repeated IP addresses
   146  	ips := []string{}
   147  	if podStatus.PodIP != "" {
   148  		ips = append(ips, podStatus.PodIP)
   149  	}
   150  
   151  	for _, podIP := range podStatus.PodIPs {
   152  		if podIP.IP != "" {
   153  			ips = append(ips, podIP.IP)
   154  		}
   155  	}
   156  
   157  	return slices.SortedUnique(ips)
   158  }
   159  
   160  // IsPodRunning returns true if the pod is considered to be in running state.
   161  // We consider a Running pod a pod that does not report a Failed nor a Succeeded
   162  // pod Phase.
   163  func IsPodRunning(status slim_corev1.PodStatus) bool {
   164  	switch status.Phase {
   165  	case slim_corev1.PodFailed, slim_corev1.PodSucceeded:
   166  		return false
   167  	}
   168  	return true
   169  }
   170  
   171  // GetClusterIPByFamily returns a service clusterip by family.
   172  // From - https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/proxy/util/utils.go#L386-L411
   173  func GetClusterIPByFamily(ipFamily slim_corev1.IPFamily, service *slim_corev1.Service) string {
   174  	// allowing skew
   175  	if len(service.Spec.IPFamilies) == 0 {
   176  		if len(service.Spec.ClusterIP) == 0 || service.Spec.ClusterIP == v1.ClusterIPNone {
   177  			return ""
   178  		}
   179  
   180  		IsIPv6Family := (ipFamily == slim_corev1.IPv6Protocol)
   181  		if IsIPv6Family == ip.IsIPv6(net.ParseIP(service.Spec.ClusterIP)) {
   182  			return service.Spec.ClusterIP
   183  		}
   184  
   185  		return ""
   186  	}
   187  
   188  	for idx, family := range service.Spec.IPFamilies {
   189  		if family == ipFamily {
   190  			if idx < len(service.Spec.ClusterIPs) {
   191  				return service.Spec.ClusterIPs[idx]
   192  			}
   193  		}
   194  	}
   195  
   196  	return ""
   197  }
   198  
   199  // nameLabelsGetter is an interface that returns the name and the labels for
   200  // the namespace.
   201  type nameLabelsGetter interface {
   202  	GetName() string
   203  	GetLabels() map[string]string
   204  }
   205  
   206  // filterPodLabels returns a copy of the given labels map, without the labels owned by Cilium.
   207  func filterPodLabels(labels map[string]string) map[string]string {
   208  	res := map[string]string{}
   209  	for k, v := range labels {
   210  		if strings.HasPrefix(k, k8sconst.LabelPrefix) {
   211  			continue
   212  		}
   213  		res[k] = v
   214  	}
   215  	return res
   216  }
   217  
   218  // SanitizePodLabels makes sure that no important pod labels were overridden manually on k8s pod
   219  // object creation.
   220  func SanitizePodLabels(podLabels map[string]string, namespace nameLabelsGetter, serviceAccount, clusterName string) map[string]string {
   221  	sanitizedLabels := filterPodLabels(podLabels)
   222  
   223  	// Sanitize namespace labels
   224  	for k, v := range namespace.GetLabels() {
   225  		sanitizedLabels[joinPath(k8sconst.PodNamespaceMetaLabels, k)] = v
   226  	}
   227  	// Sanitize namespace name label
   228  	sanitizedLabels[k8sconst.PodNamespaceLabel] = namespace.GetName()
   229  	// Sanitize service account name
   230  	if serviceAccount != "" {
   231  		sanitizedLabels[k8sconst.PolicyLabelServiceAccount] = serviceAccount
   232  	} else {
   233  		delete(sanitizedLabels, k8sconst.PolicyLabelServiceAccount)
   234  	}
   235  	// Sanitize cluster name
   236  	sanitizedLabels[k8sconst.PolicyLabelCluster] = clusterName
   237  
   238  	return sanitizedLabels
   239  }
   240  
   241  // StripPodSpecialLabels strips labels that are not supposed to be coming from a k8s pod object update.
   242  func StripPodSpecialLabels(labels map[string]string) map[string]string {
   243  	sanitizedLabels := make(map[string]string)
   244  	for k, v := range filterPodLabels(labels) {
   245  		// If the key contains the prefix for namespace labels then we will
   246  		// ignore it.
   247  		if strings.HasPrefix(k, k8sconst.PodNamespaceMetaLabels) {
   248  			continue
   249  		}
   250  		// Also ignore it if the key is a kubernetes namespace label.
   251  		if k == k8sconst.PodNamespaceLabel {
   252  			continue
   253  		}
   254  		sanitizedLabels[k] = v
   255  	}
   256  	return sanitizedLabels
   257  }
   258  
   259  // joinPath mimics JoinPath from pkg/policy/utils, which could not be imported here due to circular dependency
   260  func joinPath(a, b string) string {
   261  	return a + labelsPkg.PathDelimiter + b
   262  }