Skip to content

Commit 8bda315

Browse files
committed
connectivity: add egress-gateway-with-l7-policy test
egress-gateway-with-l7-policy checks if traffic from Pods that are selected by both Egress Gateway Policy and L7 Network Policy is properly SNATed with an Egress IP. Signed-off-by: Yusuke Suzuki <[email protected]>
1 parent 101777f commit 8bda315

8 files changed

+163
-13
lines changed

connectivity/builder/builder.go

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ func concurrentTests(connTests []*check.ConnectivityTest) error {
209209
nodeToNodeEncryption{},
210210
egressGateway{},
211211
egressGatewayExcludedCidrs{},
212+
egressGatewayWithL7Policy{},
212213
podToNodeCidrpolicy{},
213214
northSouthLoadbalancingWithL7Policy{},
214215
echoIngressL7{},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Authors of Cilium
3+
4+
package builder
5+
6+
import (
7+
_ "embed"
8+
"github.com/cilium/cilium-cli/connectivity/check"
9+
"github.com/cilium/cilium-cli/connectivity/tests"
10+
"github.com/cilium/cilium-cli/utils/features"
11+
"github.com/cilium/cilium/pkg/versioncheck"
12+
)
13+
14+
//go:embed manifests/client-egress-icmp.yaml
15+
var clientEgressICMPYAML string
16+
17+
//go:embed manifests/client-egress-l7-http-external-node.yaml
18+
var clientEgressL7HTTPExternalYAML string
19+
20+
type egressGatewayWithL7Policy struct{}
21+
22+
func (t egressGatewayWithL7Policy) build(ct *check.ConnectivityTest, templates map[string]string) {
23+
newTest("egress-gateway-with-l7-policy", ct).
24+
WithCondition(func() bool {
25+
return versioncheck.MustCompile(">=1.16.0")(ct.CiliumVersion) && ct.Params().IncludeUnsafeTests
26+
}).
27+
WithCiliumPolicy(clientEgressICMPYAML).
28+
WithCiliumPolicy(clientEgressOnlyDNSPolicyYAML). // DNS resolution only
29+
WithCiliumPolicy(clientEgressL7HTTPExternalYAML). // L7 allow policy with HTTP introspection
30+
WithCiliumEgressGatewayPolicy(check.CiliumEgressGatewayPolicyParams{
31+
Name: "cegp-sample-client",
32+
PodSelectorKind: "client",
33+
}).
34+
WithCiliumEgressGatewayPolicy(check.CiliumEgressGatewayPolicyParams{
35+
Name: "cegp-sample-echo",
36+
PodSelectorKind: "echo",
37+
}).
38+
WithIPRoutesFromOutsideToPodCIDRs().
39+
WithFeatureRequirements(
40+
features.RequireEnabled(features.EgressGateway),
41+
features.RequireEnabled(features.L7Proxy),
42+
features.RequireEnabled(features.NodeWithoutCilium),
43+
).
44+
WithScenarios(tests.EgressGateway())
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
apiVersion: "cilium.io/v2"
3+
kind: CiliumNetworkPolicy
4+
metadata:
5+
name: client-egress-icmp
6+
spec:
7+
description: "Allow clients to send ICMP"
8+
endpointSelector:
9+
matchLabels:
10+
kind: client
11+
egress:
12+
- icmps:
13+
- fields:
14+
- type: 8
15+
family: IPv4
16+
- type: 128
17+
family: IPv6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
# All clients are allowed to contact
3+
# echo-external-node.cilium-test.svc.cluster.local/client-ip
4+
# on port http-8080.
5+
# The toFQDNs section relies on DNS introspection being performed by
6+
# the client-egress-only-dns policy.
7+
apiVersion: cilium.io/v2
8+
kind: CiliumNetworkPolicy
9+
metadata:
10+
name: client-egress-l7-http-external-node
11+
spec:
12+
description: "Allow GET echo-external-node.cilium-test.svc.cluster.local:8080/client-ip"
13+
endpointSelector:
14+
matchLabels:
15+
any:kind: client
16+
egress:
17+
- toFQDNs:
18+
- matchName: "echo-external-node.cilium-test.svc.cluster.local"
19+
toPorts:
20+
- ports:
21+
- port: "8080"
22+
protocol: TCP
23+
rules:
24+
http:
25+
- method: GET
26+
path: /client-ip

connectivity/check/context.go

+18-12
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,19 @@ type ConnectivityTest struct {
5858
// Clients for source and destination clusters.
5959
clients *deploymentClients
6060

61-
ciliumPods map[string]Pod
62-
echoPods map[string]Pod
63-
echoExternalPods map[string]Pod
64-
clientPods map[string]Pod
65-
clientCPPods map[string]Pod
66-
perfClientPods []Pod
67-
perfServerPod []Pod
68-
PerfResults []common.PerfSummary
69-
echoServices map[string]Service
70-
ingressService map[string]Service
71-
k8sService Service
72-
externalWorkloads map[string]ExternalWorkload
61+
ciliumPods map[string]Pod
62+
echoPods map[string]Pod
63+
echoExternalPods map[string]Pod
64+
clientPods map[string]Pod
65+
clientCPPods map[string]Pod
66+
perfClientPods []Pod
67+
perfServerPod []Pod
68+
PerfResults []common.PerfSummary
69+
echoServices map[string]Service
70+
echoExternalServices map[string]Service
71+
ingressService map[string]Service
72+
k8sService Service
73+
externalWorkloads map[string]ExternalWorkload
7374

7475
hostNetNSPodsByNode map[string]Pod
7576
secondaryNetworkNodeIPv4 map[string]string // node name => secondary ip
@@ -207,6 +208,7 @@ func NewConnectivityTest(client *k8s.Client, p Parameters, version string, logge
207208
perfServerPod: []Pod{},
208209
PerfResults: []common.PerfSummary{},
209210
echoServices: make(map[string]Service),
211+
echoExternalServices: make(map[string]Service),
210212
ingressService: make(map[string]Service),
211213
externalWorkloads: make(map[string]ExternalWorkload),
212214
hostNetNSPodsByNode: make(map[string]Pod),
@@ -1131,6 +1133,10 @@ func (ct *ConnectivityTest) EchoServicesAll() map[string]Service {
11311133
return ct.echoServices
11321134
}
11331135

1136+
func (ct *ConnectivityTest) EchoExternalServices() map[string]Service {
1137+
return ct.echoExternalServices
1138+
}
1139+
11341140
func (ct *ConnectivityTest) ExternalEchoPods() map[string]Pod {
11351141
return ct.echoExternalPods
11361142
}

connectivity/check/deployment.go

+21
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,16 @@ func (ct *ConnectivityTest) deploy(ctx context.Context) error {
905905
if err != nil {
906906
return fmt.Errorf("unable to create deployment %s: %s", echoExternalNodeDeploymentName, err)
907907
}
908+
909+
svc := newService(echoExternalNodeDeploymentName,
910+
map[string]string{"name": echoExternalNodeDeploymentName, "kind": kindEchoExternalNodeName},
911+
map[string]string{"kind": kindEchoExternalNodeName}, "http", 8080)
912+
svc.Spec.ClusterIP = corev1.ClusterIPNone
913+
svc.Spec.Type = corev1.ServiceTypeClusterIP
914+
_, err := ct.clients.src.CreateService(ctx, ct.params.TestNamespace, svc, metav1.CreateOptions{})
915+
if err != nil {
916+
return fmt.Errorf("unable to create service %s: %w", echoExternalNodeDeploymentName, err)
917+
}
908918
}
909919
} else {
910920
ct.Infof("Skipping tests that require a node Without Cilium")
@@ -1243,6 +1253,17 @@ func (ct *ConnectivityTest) validateDeployment(ctx context.Context) error {
12431253
port: uint32(ct.Params().ExternalDeploymentPort), // listen port of the echo server inside the container
12441254
}
12451255
}
1256+
1257+
echoExternalServices, err := ct.clients.dst.ListServices(ctx, ct.params.TestNamespace, metav1.ListOptions{LabelSelector: "kind=" + kindEchoExternalNodeName})
1258+
if err != nil {
1259+
return fmt.Errorf("unable to list echo external services: %w", err)
1260+
}
1261+
1262+
for _, echoExternalService := range echoExternalServices.Items {
1263+
ct.echoExternalServices[echoExternalService.Name] = Service{
1264+
Service: echoExternalService.DeepCopy(),
1265+
}
1266+
}
12461267
}
12471268

12481269
for _, cp := range ct.clientPods {

connectivity/check/peer.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func (s Service) Path() string {
201201
// Address returns the network address of the Service.
202202
func (s Service) Address(family features.IPFamily) string {
203203
// If the cluster IP is empty (headless service case) or the IP family is set to any, return the service name
204-
if s.Service.Spec.ClusterIP == "" || family == features.IPFamilyAny {
204+
if s.Service.Spec.ClusterIP == "" || s.Service.Spec.ClusterIP == corev1.ClusterIPNone || family == features.IPFamilyAny {
205205
return fmt.Sprintf("%s.%s", s.Service.Name, s.Service.Namespace)
206206
}
207207

@@ -256,6 +256,12 @@ func (s Service) ToNodeportService(node *corev1.Node) NodeportService {
256256
}
257257
}
258258

259+
func (s Service) ToEchoIPService() EchoIPService {
260+
return EchoIPService{
261+
Service: s,
262+
}
263+
}
264+
259265
// NodeportService wraps a Service and exposes it through its nodeport, acting as a peer in a connectivity test.
260266
// It implements interface TestPeer.
261267
type NodeportService struct {
@@ -499,3 +505,11 @@ type EchoIPPod struct {
499505
func (p EchoIPPod) Path() string {
500506
return p.path + "/client-ip"
501507
}
508+
509+
type EchoIPService struct {
510+
Service
511+
}
512+
513+
func (s EchoIPService) Path() string {
514+
return s.URLPath + "/client-ip"
515+
}

connectivity/tests/egressgateway.go

+20
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,26 @@ func (s *egressGateway) Run(ctx context.Context, t *check.Test) {
235235
i++
236236
}
237237

238+
// Traffic matching an egress gateway policy should leave the cluster masqueraded with the egress IP (pod to external service using DNS)
239+
i = 0
240+
for _, client := range ct.ClientPods() {
241+
client := client
242+
243+
for _, externalEchoSvc := range ct.EchoExternalServices() {
244+
externalEcho := externalEchoSvc.ToEchoIPService()
245+
246+
t.NewAction(s, fmt.Sprintf("curl-external-echo-service-%d", i), &client, externalEcho, features.IPFamilyV4).Run(func(a *check.Action) {
247+
a.ExecInPod(ctx, ct.CurlCommandWithOutput(externalEcho, features.IPFamilyV4, "-4"))
248+
clientIP := extractClientIPFromResponse(a.CmdOutput())
249+
250+
if !clientIP.Equal(egressGatewayNodeInternalIP) {
251+
t.Fatal("Request reached external echo service with wrong source IP")
252+
}
253+
})
254+
i++
255+
}
256+
}
257+
238258
// Traffic matching an egress gateway policy should leave the cluster masqueraded with the egress IP (pod to external service)
239259
i = 0
240260
for _, client := range ct.ClientPods() {

0 commit comments

Comments
 (0)