github.com/cilium/cilium@v1.16.2/pkg/hubble/dropeventemitter/dropeventemitter_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package dropeventemitter 5 6 import ( 7 "context" 8 "fmt" 9 "testing" 10 11 flowpb "github.com/cilium/cilium/api/v1/flow" 12 "github.com/cilium/cilium/pkg/identity" 13 slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 14 slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 15 16 "github.com/stretchr/testify/assert" 17 ) 18 19 const ( 20 fakePodName = "pod" 21 fakePodUid = "79f04581-a0e7-4a42-a020-db51cf21a605" 22 ) 23 24 func TestEndpointToString(t *testing.T) { 25 tests := []struct { 26 name string 27 ip string 28 endpoint *flowpb.Endpoint 29 expect string 30 }{ 31 { 32 name: fakePodName, 33 ip: "1.2.3.4", 34 endpoint: &flowpb.Endpoint{PodName: fakePodName, Namespace: "namespace"}, 35 expect: "namespace/pod (1.2.3.4)", 36 }, 37 { 38 name: "node", 39 ip: "1.2.3.4", 40 endpoint: &flowpb.Endpoint{Identity: identity.ReservedIdentityRemoteNode.Uint32()}, 41 expect: identity.ReservedIdentityRemoteNode.String() + " (1.2.3.4)", 42 }, 43 { 44 name: "unknown", 45 ip: "1.2.3.4", 46 endpoint: &flowpb.Endpoint{Identity: identity.MaxLocalIdentity.Uint32() + 1}, 47 expect: "1.2.3.4", 48 }, 49 } 50 for _, tt := range tests { 51 t.Run(tt.name, func(t *testing.T) { 52 e := &DropEventEmitter{} 53 str := e.endpointToString(tt.ip, tt.endpoint) 54 assert.Equal(t, str, tt.expect) 55 }) 56 } 57 } 58 59 func TestL4protocolToString(t *testing.T) { 60 tests := []struct { 61 name string 62 l4 *flowpb.Layer4 63 expect string 64 }{ 65 { 66 name: "udp/512", 67 l4: &flowpb.Layer4{Protocol: &flowpb.Layer4_UDP{UDP: &flowpb.UDP{DestinationPort: 512}}}, 68 expect: "UDP/512", 69 }, 70 { 71 name: "tcp/443", 72 l4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{DestinationPort: 443}}}, 73 expect: "TCP/443", 74 }, 75 { 76 name: "unknown", 77 l4: &flowpb.Layer4{}, 78 expect: "", 79 }, 80 } 81 for _, tt := range tests { 82 t.Run(tt.name, func(t *testing.T) { 83 e := &DropEventEmitter{} 84 str := e.l4protocolToString(tt.l4) 85 assert.Equal(t, str, tt.expect) 86 }) 87 } 88 } 89 90 func TestProcessFlow(t *testing.T) { 91 tests := []struct { 92 name string 93 flow *flowpb.Flow 94 expect string 95 }{ 96 { 97 name: "valid ingress drop event", 98 flow: &flowpb.Flow{ 99 Verdict: flowpb.Verdict_DROPPED, 100 DropReasonDesc: flowpb.DropReason_POLICY_DENIED, 101 TrafficDirection: flowpb.TrafficDirection_INGRESS, 102 IP: &flowpb.IP{Source: "1.2.3.4", Destination: "5.6.7.8"}, 103 Source: &flowpb.Endpoint{}, 104 Destination: &flowpb.Endpoint{Namespace: "namespace", PodName: fakePodName}, 105 L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{DestinationPort: 443}}}, 106 }, 107 expect: "Incoming packet dropped (policy_denied) from unknown (1.2.3.4) TCP/443", 108 }, 109 { 110 name: "valid egress drop event to node", 111 flow: &flowpb.Flow{ 112 Verdict: flowpb.Verdict_DROPPED, 113 DropReasonDesc: flowpb.DropReason_POLICY_DENIED, 114 TrafficDirection: flowpb.TrafficDirection_EGRESS, 115 IP: &flowpb.IP{Source: "1.2.3.4", Destination: "5.6.7.8"}, 116 Source: &flowpb.Endpoint{Namespace: "namespace", PodName: fakePodName}, 117 Destination: &flowpb.Endpoint{Identity: identity.ReservedIdentityRemoteNode.Uint32()}, 118 L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_UDP{UDP: &flowpb.UDP{DestinationPort: 512}}}, 119 }, 120 expect: "Outgoing packet dropped (policy_denied) to remote-node (5.6.7.8) UDP/512", 121 }, 122 { 123 name: "ingress drop event not matching reason", 124 flow: &flowpb.Flow{ 125 Verdict: flowpb.Verdict_DROPPED, 126 DropReasonDesc: flowpb.DropReason_AUTH_REQUIRED, 127 TrafficDirection: flowpb.TrafficDirection_INGRESS, 128 IP: &flowpb.IP{Source: "1.2.3.4", Destination: "5.6.7.8"}, 129 Source: &flowpb.Endpoint{}, 130 Destination: &flowpb.Endpoint{Namespace: "namespace", PodName: fakePodName}, 131 L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{DestinationPort: 443}}}, 132 }, 133 expect: "", 134 }, 135 { 136 name: "ingress verdict is not dropped", 137 flow: &flowpb.Flow{ 138 Verdict: flowpb.Verdict_ERROR, 139 DropReasonDesc: flowpb.DropReason_POLICY_DENIED, 140 TrafficDirection: flowpb.TrafficDirection_INGRESS, 141 IP: &flowpb.IP{Source: "1.2.3.4", Destination: "5.6.7.8"}, 142 Source: &flowpb.Endpoint{}, 143 Destination: &flowpb.Endpoint{Namespace: "namespace", PodName: fakePodName}, 144 L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{DestinationPort: 443}}}, 145 }, 146 expect: "", 147 }, 148 { 149 name: "ingress but no destination pod", 150 flow: &flowpb.Flow{ 151 Verdict: flowpb.Verdict_DROPPED, 152 DropReasonDesc: flowpb.DropReason_POLICY_DENIED, 153 TrafficDirection: flowpb.TrafficDirection_INGRESS, 154 IP: &flowpb.IP{Source: "1.2.3.4", Destination: "5.6.7.8"}, 155 Source: &flowpb.Endpoint{Namespace: "namespace", PodName: fakePodName}, 156 Destination: &flowpb.Endpoint{}, 157 L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{DestinationPort: 443}}}, 158 }, 159 expect: "", 160 }, 161 { 162 name: "egress but no source pod", 163 flow: &flowpb.Flow{ 164 Verdict: flowpb.Verdict_DROPPED, 165 DropReasonDesc: flowpb.DropReason_POLICY_DENIED, 166 TrafficDirection: flowpb.TrafficDirection_EGRESS, 167 IP: &flowpb.IP{Source: "1.2.3.4", Destination: "5.6.7.8"}, 168 Source: &flowpb.Endpoint{}, 169 Destination: &flowpb.Endpoint{}, 170 L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{DestinationPort: 443}}}, 171 }, 172 expect: "", 173 }, 174 } 175 for _, tt := range tests { 176 t.Run(tt.name, func(t *testing.T) { 177 fakeRecorder := &FakeRecorder{ 178 Events: make(chan string, 3), 179 IncludeObject: true, 180 } 181 e := &DropEventEmitter{ 182 reasons: []string{"policy_denied"}, 183 recorder: fakeRecorder, 184 k8sWatcher: &fakeK8SWatcher{}, 185 } 186 if err := e.ProcessFlow(context.Background(), tt.flow); err != nil { 187 t.Errorf("DropEventEmitter.ProcessFlow() error = %v", err) 188 } 189 if tt.expect == "" { 190 assert.Len(t, fakeRecorder.Events, 0) 191 } else { 192 assert.Len(t, fakeRecorder.Events, 1) 193 event := <-fakeRecorder.Events 194 assert.Contains(t, event, tt.expect) 195 if tt.flow.Destination.PodName == fakePodName && tt.flow.TrafficDirection == flowpb.TrafficDirection_EGRESS { 196 assert.Contains(t, event, fakePodUid) 197 } 198 } 199 }) 200 } 201 } 202 203 type fakeK8SWatcher struct { 204 } 205 206 func (k *fakeK8SWatcher) GetCachedNamespace(namespace string) (*slim_corev1.Namespace, error) { 207 return nil, nil 208 } 209 func (k *fakeK8SWatcher) GetCachedPod(namespace, name string) (*slim_corev1.Pod, error) { 210 if name == fakePodName { 211 return &slim_corev1.Pod{ 212 ObjectMeta: slim_metav1.ObjectMeta{ 213 Name: fakePodName, 214 UID: fakePodUid, 215 }, 216 }, nil 217 } 218 return nil, fmt.Errorf("pod not found in cache : %s", name) 219 }