/
egressgateway-excluded-cirds.go
169 lines (139 loc) · 5.02 KB
/
egressgateway-excluded-cirds.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium
package tests
import (
"context"
"encoding/json"
"fmt"
"net"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/cilium/cilium-cli/connectivity/check"
"github.com/cilium/cilium-cli/defaults"
"github.com/cilium/cilium-cli/internal/utils"
)
// EgressGatewayExcludedCIDRs is a test case which, given the cegp-sample
// CiliumEgressGatewayExcludedCIDRsPolicy targeting:
// - a couple of client pods (kind=client) as source
// - the 0.0.0.0/0 destination CIDR
// - kind-worker2 as gateway node
//
// ensures that traffic from both clients reaches the echo-external service with
// the egress IP of the gateway node.
func EgressGatewayExcludedCIDRs() check.Scenario {
return &egressGatewayExcludedCIDRs{}
}
type egressGatewayExcludedCIDRs struct {
egressGatewayNode string
}
func (s *egressGatewayExcludedCIDRs) Name() string {
return "egress-gateway-excluded-cidrs"
}
func (s *egressGatewayExcludedCIDRs) Run(ctx context.Context, t *check.Test) {
ct := t.Context()
s.egressGatewayNode = t.EgressGatewayNode()
if s.egressGatewayNode == "" {
t.Fatal("Cannot get egress gateway node")
}
s.waitForBpfPolicyEntries(ctx, t)
// Traffic matching an egress gateway policy should leave the cluster masqueraded with the egress IP (pod to external service)
i := 0
for _, client := range ct.ClientPods() {
client := client
for _, externalEcho := range ct.ExternalEchoPods() {
t.NewAction(s, fmt.Sprintf("curl-%d", i), &client, externalEcho, check.IPFamilyV4).Run(func(a *check.Action) {
a.ExecInPod(ctx, ct.CurlClientIPCommand(externalEcho, check.IPFamilyV4))
clientIP := extractClientIPFromResponse(a.CmdOutput())
if !clientIP.Equal(net.ParseIP(client.Pod.Status.HostIP)) {
t.Fatal("Request reached external echo service with wrong source IP")
}
})
i++
}
}
}
// bpfEgressGatewayExcludedCIDRsPolicyEntry represents an entry in the BPF egress gateway
// policy map
type bpfEgressGatewayExcludedCIDRsPolicyEntry struct {
SourceIP string
DestCIDR string
EgressIP string
GatewayIP string
}
// matches is an helper used to compare the receiver bpfEgressGatewayExcludedCIDRsPolicyEntry
// with another entry
func (e *bpfEgressGatewayExcludedCIDRsPolicyEntry) matches(t bpfEgressGatewayExcludedCIDRsPolicyEntry) bool {
return t.SourceIP == e.SourceIP &&
t.DestCIDR == e.DestCIDR &&
t.EgressIP == e.EgressIP &&
t.GatewayIP == e.GatewayIP
}
// waitForBpfPolicyEntries waits for the egress gateway policy maps on each node
// to be populated with the entries for the cegp-sample CiliumEgressGatewayExcludedCIDRsPolicy
func (s *egressGatewayExcludedCIDRs) waitForBpfPolicyEntries(ctx context.Context, t *check.Test) {
ct := t.Context()
w := utils.NewWaitObserver(ctx, utils.WaitParameters{Timeout: 10 * time.Second})
defer w.Cancel()
ensureBpfPolicyEntries := func() error {
gatewayNodeInternalIP := getGatewayNodeInternalIP(ct, s.egressGatewayNode)
if gatewayNodeInternalIP == nil {
t.Fatalf("Cannot retrieve internal IP of gateway node")
}
for _, ciliumPod := range ct.CiliumPods() {
for _, nodeWithoutCilium := range t.NodesWithoutCilium() {
node, err := t.Context().K8sClient().GetNode(context.Background(), nodeWithoutCilium, metav1.GetOptions{})
if err != nil {
t.Fatalf("Cannot retrieve external node")
}
egressIP := "0.0.0.0"
if ciliumPod.Pod.Spec.NodeName == s.egressGatewayNode {
egressIP = gatewayNodeInternalIP.String()
}
targetEntries := []bpfEgressGatewayExcludedCIDRsPolicyEntry{}
for _, client := range ct.ClientPods() {
targetEntries = append(targetEntries,
bpfEgressGatewayExcludedCIDRsPolicyEntry{
SourceIP: client.Pod.Status.PodIP,
DestCIDR: "0.0.0.0/0",
EgressIP: egressIP,
GatewayIP: gatewayNodeInternalIP.String(),
})
targetEntries = append(targetEntries,
bpfEgressGatewayExcludedCIDRsPolicyEntry{
SourceIP: client.Pod.Status.PodIP,
DestCIDR: fmt.Sprintf("%s/32", node.Status.Addresses[0].Address),
EgressIP: egressIP,
GatewayIP: "Excluded CIDR",
})
}
cmd := strings.Split("cilium bpf egress list -o json", " ")
stdout, err := ciliumPod.K8sClient.ExecInPod(ctx, ciliumPod.Pod.Namespace, ciliumPod.Pod.Name, defaults.AgentContainerName, cmd)
if err != nil {
t.Fatal("failed to run cilium bpf egress list command: %w", err)
}
entries := []bpfEgressGatewayExcludedCIDRsPolicyEntry{}
json.Unmarshal(stdout.Bytes(), &entries)
nextTargetEntry:
for _, targetEntry := range targetEntries {
for _, entry := range entries {
if targetEntry.matches(entry) {
continue nextTargetEntry
}
}
return fmt.Errorf("Could not find egress gateway policy entry matching %+v", targetEntry)
}
}
}
return nil
}
for {
if err := ensureBpfPolicyEntries(); err != nil {
if err := w.Retry(err); err != nil {
t.Fatal("Failed to ensure egress gateway policy map is properly populated:", err)
}
continue
}
return
}
}