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

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package correlation
     5  
     6  import (
     7  	"github.com/sirupsen/logrus"
     8  
     9  	flowpb "github.com/cilium/cilium/api/v1/flow"
    10  	"github.com/cilium/cilium/pkg/hubble/parser/getters"
    11  	"github.com/cilium/cilium/pkg/identity"
    12  	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
    13  	"github.com/cilium/cilium/pkg/labels"
    14  	"github.com/cilium/cilium/pkg/logging"
    15  	"github.com/cilium/cilium/pkg/logging/logfields"
    16  	monitorAPI "github.com/cilium/cilium/pkg/monitor/api"
    17  	"github.com/cilium/cilium/pkg/policy"
    18  	"github.com/cilium/cilium/pkg/policy/trafficdirection"
    19  	"github.com/cilium/cilium/pkg/source"
    20  	"github.com/cilium/cilium/pkg/u8proto"
    21  )
    22  
    23  var logger = logging.DefaultLogger.WithField(logfields.LogSubsys, "hubble-flow-policy-correlation")
    24  
    25  // CorrelatePolicy updates the IngressAllowedBy/EgressAllowedBy fields on the
    26  // provided flow.
    27  func CorrelatePolicy(endpointGetter getters.EndpointGetter, f *flowpb.Flow) {
    28  	if f.GetEventType().GetType() != int32(monitorAPI.MessageTypePolicyVerdict) {
    29  		// If it's not a policy verdict, we don't care.
    30  		return
    31  	}
    32  
    33  	// We are only interested in flows which are either allowed (i.e. the verdict is either
    34  	// FORWARDED or REDIRECTED) or explicitly denied (i.e. DROPPED, and matched by a deny policy),
    35  	// since we cannot usefully annotate the verdict otherwise. (Put differently, which policy
    36  	// should be listed in {in|e}gress_denied_by for an unmatched flow?)
    37  	verdict := f.GetVerdict()
    38  	allowed := verdict == flowpb.Verdict_FORWARDED || verdict == flowpb.Verdict_REDIRECTED
    39  	denied := verdict == flowpb.Verdict_DROPPED && f.GetDropReasonDesc() == flowpb.DropReason_POLICY_DENY
    40  	if !(allowed || denied) {
    41  		return
    42  	}
    43  
    44  	// extract fields relevant for looking up the policy
    45  	direction, endpointID, remoteIdentity, proto, dport := extractFlowKey(f)
    46  	if dport == 0 || proto == 0 {
    47  		logger.WithField(logfields.EndpointID, endpointID).Debug("failed to extract flow key")
    48  		return
    49  	}
    50  
    51  	// obtain reference to endpoint on which the policy verdict was taken
    52  	epInfo, ok := endpointGetter.GetEndpointInfoByID(endpointID)
    53  	if !ok {
    54  		logger.WithField(logfields.EndpointID, endpointID).Debug("failed to lookup endpoint")
    55  		return
    56  	}
    57  
    58  	derivedFrom, rev, ok := lookupPolicyForKey(epInfo, policy.Key{
    59  		Identity:         uint32(remoteIdentity),
    60  		DestPort:         dport,
    61  		Nexthdr:          uint8(proto),
    62  		TrafficDirection: uint8(direction),
    63  	}, f.GetPolicyMatchType())
    64  	if !ok {
    65  		logger.WithFields(logrus.Fields{
    66  			logfields.Identity:         remoteIdentity,
    67  			logfields.Port:             dport,
    68  			logfields.Protocol:         proto,
    69  			logfields.TrafficDirection: direction,
    70  		}).Debug("unable to find policy for policy verdict notification")
    71  		return
    72  	}
    73  
    74  	rules := toProto(derivedFrom, rev)
    75  	switch {
    76  	case direction == trafficdirection.Egress && allowed:
    77  		f.EgressAllowedBy = rules
    78  	case direction == trafficdirection.Egress && denied:
    79  		f.EgressDeniedBy = rules
    80  	case direction == trafficdirection.Ingress && allowed:
    81  		f.IngressAllowedBy = rules
    82  	case direction == trafficdirection.Ingress && denied:
    83  		f.IngressDeniedBy = rules
    84  	}
    85  }
    86  
    87  func extractFlowKey(f *flowpb.Flow) (
    88  	direction trafficdirection.TrafficDirection,
    89  	endpointID uint16,
    90  	remoteIdentity identity.NumericIdentity,
    91  	proto u8proto.U8proto,
    92  	dport uint16) {
    93  
    94  	switch f.GetTrafficDirection() {
    95  	case flowpb.TrafficDirection_EGRESS:
    96  		direction = trafficdirection.Egress
    97  		// We only get a uint32 because proto has no 16-bit types.
    98  		endpointID = uint16(f.GetSource().GetID())
    99  		remoteIdentity = identity.NumericIdentity(f.GetDestination().GetIdentity())
   100  	case flowpb.TrafficDirection_INGRESS:
   101  		direction = trafficdirection.Ingress
   102  		endpointID = uint16(f.GetDestination().GetID())
   103  		remoteIdentity = identity.NumericIdentity(f.GetSource().GetIdentity())
   104  	default:
   105  		direction = trafficdirection.Invalid
   106  		endpointID = 0
   107  		remoteIdentity = identity.IdentityUnknown
   108  	}
   109  
   110  	if tcp := f.GetL4().GetTCP(); tcp != nil {
   111  		proto = u8proto.TCP
   112  		dport = uint16(tcp.GetDestinationPort())
   113  	} else if udp := f.GetL4().GetUDP(); udp != nil {
   114  		proto = u8proto.UDP
   115  		dport = uint16(udp.GetDestinationPort())
   116  	} else if icmpv4 := f.GetL4().GetICMPv4(); icmpv4 != nil {
   117  		proto = u8proto.ICMP
   118  		dport = uint16(icmpv4.Type)
   119  	} else if icmpv6 := f.GetL4().GetICMPv6(); icmpv6 != nil {
   120  		proto = u8proto.ICMPv6
   121  		dport = uint16(icmpv6.Type)
   122  	} else if sctp := f.GetL4().GetSCTP(); sctp != nil {
   123  		proto = u8proto.SCTP
   124  		dport = uint16(sctp.GetDestinationPort())
   125  	} else {
   126  		proto = u8proto.ANY
   127  		dport = 0
   128  	}
   129  
   130  	return
   131  }
   132  
   133  func lookupPolicyForKey(ep getters.EndpointInfo, key policy.Key, matchType uint32) (derivedFrom labels.LabelArrayList, rev uint64, ok bool) {
   134  	switch matchType {
   135  	case monitorAPI.PolicyMatchL3L4, monitorAPI.PolicyMatchL4Only:
   136  		// Check for L4 policy rules
   137  		derivedFrom, rev, ok = ep.GetRealizedPolicyRuleLabelsForKey(key)
   138  	case monitorAPI.PolicyMatchL3Only:
   139  		// Check for L3 policy rules
   140  		derivedFrom, rev, ok = ep.GetRealizedPolicyRuleLabelsForKey(policy.Key{
   141  			Identity:         key.Identity,
   142  			DestPort:         0,
   143  			InvertedPortMask: 0xffff, // this is a wildcard
   144  			Nexthdr:          0,
   145  			TrafficDirection: key.TrafficDirection,
   146  		})
   147  	case monitorAPI.PolicyMatchAll:
   148  		// Check for allow-all policy rules
   149  		derivedFrom, rev, ok = ep.GetRealizedPolicyRuleLabelsForKey(policy.Key{
   150  			Identity:         0,
   151  			DestPort:         0,
   152  			InvertedPortMask: 0xffff, // this is a wildcard
   153  			Nexthdr:          0,
   154  			TrafficDirection: key.TrafficDirection,
   155  		})
   156  	}
   157  
   158  	return derivedFrom, rev, ok
   159  }
   160  
   161  func toProto(derivedFrom labels.LabelArrayList, rev uint64) (policies []*flowpb.Policy) {
   162  	for i, lbl := range derivedFrom {
   163  		// derivedFrom may contain a duplicate policies if the policy had
   164  		// multiple that contributed to the same policy map entry.
   165  		// We can easily detect the duplicates here, because derivedFrom is
   166  		// sorted.
   167  		if i > 0 && lbl.Equals(derivedFrom[i-1]) {
   168  			continue
   169  		}
   170  
   171  		policy := &flowpb.Policy{
   172  			Labels:   lbl.GetModel(),
   173  			Revision: rev,
   174  		}
   175  
   176  		var ns, name string
   177  		for _, l := range lbl {
   178  			if l.Source == string(source.Kubernetes) {
   179  				switch l.Key {
   180  				case k8sConst.PolicyLabelName:
   181  					name = l.Value
   182  				case k8sConst.PolicyLabelNamespace:
   183  					ns = l.Value
   184  				}
   185  			}
   186  
   187  			if name != "" && ns != "" {
   188  				policy.Name = name
   189  				policy.Namespace = ns
   190  				break
   191  			}
   192  		}
   193  
   194  		policies = append(policies, policy)
   195  	}
   196  
   197  	return policies
   198  }