github.com/cilium/cilium@v1.16.2/pkg/hubble/parser/common/endpoint.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Hubble 3 4 package common 5 6 import ( 7 "net/netip" 8 9 "github.com/sirupsen/logrus" 10 11 pb "github.com/cilium/cilium/api/v1/flow" 12 "github.com/cilium/cilium/pkg/hubble/parser/getters" 13 "github.com/cilium/cilium/pkg/identity" 14 k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io" 15 "github.com/cilium/cilium/pkg/k8s/utils" 16 "github.com/cilium/cilium/pkg/logging" 17 "github.com/cilium/cilium/pkg/logging/logfields" 18 "github.com/cilium/cilium/pkg/time" 19 ) 20 21 type DatapathContext struct { 22 SrcIP netip.Addr 23 SrcLabelID uint32 24 DstIP netip.Addr 25 DstLabelID uint32 26 TraceObservationPoint pb.TraceObservationPoint 27 } 28 29 type EndpointResolver struct { 30 log logrus.FieldLogger 31 logLimiter logging.Limiter 32 endpointGetter getters.EndpointGetter 33 identityGetter getters.IdentityGetter 34 ipGetter getters.IPGetter 35 } 36 37 func NewEndpointResolver( 38 log logrus.FieldLogger, 39 endpointGetter getters.EndpointGetter, 40 identityGetter getters.IdentityGetter, 41 ipGetter getters.IPGetter, 42 ) *EndpointResolver { 43 return &EndpointResolver{ 44 log: log, 45 logLimiter: logging.NewLimiter(30*time.Second, 1), 46 endpointGetter: endpointGetter, 47 identityGetter: identityGetter, 48 ipGetter: ipGetter, 49 } 50 } 51 52 func (r *EndpointResolver) ResolveEndpoint(ip netip.Addr, datapathSecurityIdentity uint32, context DatapathContext) *pb.Endpoint { 53 // The datapathSecurityIdentity parameter is the numeric security identity 54 // obtained from the datapath. 55 // The numeric identity from the datapath can differ from the one we obtain 56 // from user-space (e.g. the endpoint manager or the IP cache), because 57 // the identity could have changed between the time the datapath event was 58 // created and the time the event reaches the Hubble parser. 59 // To aid in troubleshooting, we want to preserve what the datapath observed 60 // when it made the policy decision. 61 resolveIdentityConflict := func(userspaceID identity.NumericIdentity, isLocalEndpoint bool) uint32 { 62 // if the datapath did not provide an identity (e.g. FROM_LXC trace 63 // points), use what we have in the user-space cache 64 datapathID := identity.NumericIdentity(datapathSecurityIdentity) 65 if datapathID == identity.IdentityUnknown { 66 return userspaceID.Uint32() 67 } 68 69 // Log any identity discrepancies, unless or this is a known case where 70 // Hubble does not have the full picture (see inline comments below each case) 71 // or we've hit the log rate limit 72 if datapathID != userspaceID { 73 if context.TraceObservationPoint == pb.TraceObservationPoint_TO_OVERLAY && 74 ip == context.SrcIP && datapathID.Uint32() == context.SrcLabelID && 75 datapathID == identity.ReservedIdentityRemoteNode && 76 userspaceID == identity.ReservedIdentityHost { 77 // Ignore 78 // 79 // When encapsulating a packet for sending via the overlay network, if the source 80 // seclabel = HOST_ID, then we reassign seclabel with LOCAL_NODE_ID and then send 81 // a trace notify. 82 } else if context.TraceObservationPoint == pb.TraceObservationPoint_TO_OVERLAY && 83 ip == context.SrcIP && datapathID.Uint32() == context.SrcLabelID && 84 !datapathID.IsReservedIdentity() && userspaceID == identity.ReservedIdentityHost { 85 // Ignore 86 // 87 // An IPSec encrypted packet will have the local cilium_host IP as the source 88 // address, but the datapath seclabel will be the one of the source pod. 89 } else if context.TraceObservationPoint == pb.TraceObservationPoint_FROM_ENDPOINT && 90 ip == context.SrcIP && datapathID.Uint32() == context.SrcLabelID && 91 (datapathID == identity.ReservedIdentityHealth || !datapathID.IsReservedIdentity()) && 92 userspaceID.IsWorld() { 93 // Ignore 94 // 95 // Sometimes packets from endpoint link-local addresses are intercepted by 96 // cil_from_container. Because link-local addresses are not stored in the IP cache, 97 // Hubble assigns them WORLD_ID. 98 } else if context.TraceObservationPoint == pb.TraceObservationPoint_FROM_HOST && 99 ip == context.SrcIP && datapathID.Uint32() == context.SrcLabelID && 100 datapathID.IsWorld() && userspaceID == identity.ReservedIdentityKubeAPIServer { 101 // Ignore 102 // 103 // When a pod sends a packet to the Kubernetes API, its IP is masqueraded and then 104 // when it receives a response and the masquerade is reversed, cil_from_host 105 // determines that the source ID is WORLD_ID because there is no packet mark. 106 } else if (context.TraceObservationPoint == pb.TraceObservationPoint_FROM_HOST || 107 context.TraceObservationPoint == pb.TraceObservationPoint_TO_OVERLAY) && 108 ip == context.SrcIP && datapathID.Uint32() == context.SrcLabelID && 109 isLocalEndpoint && userspaceID == identity.ReservedIdentityHost { 110 // Ignore 111 // 112 // When proxied packets (via Cilium DNS proxy) are sent from the host their source 113 // IP is that of the host, yet their security identity is retained from the 114 // original source pod. 115 } else if context.TraceObservationPoint == pb.TraceObservationPoint_TO_ENDPOINT && 116 ip == context.SrcIP && datapathID.Uint32() == context.SrcLabelID && 117 !datapathID.IsReservedIdentity() && 118 (userspaceID == identity.ReservedIdentityHost || userspaceID == identity.ReservedIdentityRemoteNode) { 119 // Ignore 120 // 121 // When proxied packets (via Cilium DNS proxy) are received by the destination 122 // host their source IP is that of the proxy, yet their security identity is 123 // retained from the original source pod. This is a similar case to #4, but on the 124 // receiving side. 125 } else if r.logLimiter.Allow() { 126 r.log.WithFields(logrus.Fields{ 127 "datapath-identity": datapathID.Uint32(), 128 "userspace-identity": userspaceID.Uint32(), 129 "context": logfields.Repr(context), 130 logfields.IPAddr: ip, 131 }).Debugf("stale identity observed") 132 } 133 } 134 135 return datapathID.Uint32() 136 } 137 138 // for local endpoints, use the available endpoint information 139 if r.endpointGetter != nil { 140 if ep, ok := r.endpointGetter.GetEndpointInfo(ip); ok { 141 epIdentity := resolveIdentityConflict(ep.GetIdentity(), true) 142 e := &pb.Endpoint{ 143 ID: uint32(ep.GetID()), 144 Identity: epIdentity, 145 Namespace: ep.GetK8sNamespace(), 146 Labels: SortAndFilterLabels(r.log, ep.GetLabels(), identity.NumericIdentity(epIdentity)), 147 PodName: ep.GetK8sPodName(), 148 } 149 if pod := ep.GetPod(); pod != nil { 150 workload, workloadTypeMeta, ok := utils.GetWorkloadMetaFromPod(pod) 151 if ok { 152 e.Workloads = []*pb.Workload{{Kind: workloadTypeMeta.Kind, Name: workload.Name}} 153 } 154 } 155 return e 156 } 157 } 158 159 // for remote endpoints, assemble the information via ip and identity 160 numericIdentity := datapathSecurityIdentity 161 var namespace, podName string 162 if r.ipGetter != nil { 163 if ipIdentity, ok := r.ipGetter.LookupSecIDByIP(ip); ok { 164 numericIdentity = resolveIdentityConflict(ipIdentity.ID, false) 165 } 166 if meta := r.ipGetter.GetK8sMetadata(ip); meta != nil { 167 namespace, podName = meta.Namespace, meta.PodName 168 } 169 } 170 var labels []string 171 var clusterName string 172 if r.identityGetter != nil { 173 if id, err := r.identityGetter.GetIdentity(numericIdentity); err != nil { 174 r.log.WithError(err).WithField("identity", numericIdentity). 175 Debug("failed to resolve identity") 176 } else { 177 labels = SortAndFilterLabels(r.log, id.Labels.GetModel(), identity.NumericIdentity(numericIdentity)) 178 clusterName = (id.Labels[k8sConst.PolicyLabelCluster]).Value 179 } 180 } 181 182 return &pb.Endpoint{ 183 Identity: numericIdentity, 184 ClusterName: clusterName, 185 Namespace: namespace, 186 Labels: labels, 187 PodName: podName, 188 } 189 }