From 4cce09145e080c9a1564bc6e47137b42e852291a Mon Sep 17 00:00:00 2001 From: Gilberto Bertin Date: Tue, 30 May 2023 15:02:12 +0200 Subject: [PATCH] connectivity: add full egress gateway test suite This commit reimplement the Ginkgo test suite for egress gateway as a connectivity test for the CLI. The changes are split in 2 test suites: * egress-gateway, which tests connectivity for pods matched by an egress gateway policy: pod to host, pod to service, pod to external IP, reply traffic for services and pods * egress-gateway-excluded-cidrs, which tests the excludedCIDRs property and ensure traffic matching an excluded CIDR does not get masqueraded with the egress IP Signed-off-by: Gilberto Bertin --- connectivity/check/context.go | 7 + connectivity/check/test.go | 32 +- .../egress-gateway-policy-excluded-cidrs.yaml | 18 + .../manifests/egress-gateway-policy.yaml | 16 + connectivity/suite.go | 33 +- connectivity/tests/egressgateway.go | 397 +++++++++++++----- k8s/client.go | 4 + 7 files changed, 395 insertions(+), 112 deletions(-) create mode 100644 connectivity/manifests/egress-gateway-policy-excluded-cidrs.yaml diff --git a/connectivity/check/context.go b/connectivity/check/context.go index a924183eed..1ae814a489 100644 --- a/connectivity/check/context.go +++ b/connectivity/check/context.go @@ -818,6 +818,13 @@ func (ct *ConnectivityTest) PingCommand(peer TestPeer, ipFam IPFamily) []string return cmd } +func (ct *ConnectivityTest) DigCommand(peer TestPeer, ipFam IPFamily) []string { + cmd := []string{"dig", "+time=2", "kubernetes"} + + cmd = append(cmd, fmt.Sprintf("@%s", peer.Address(ipFam))) + return cmd +} + func (ct *ConnectivityTest) RandomClientPod() *Pod { for _, p := range ct.clientPods { return &p diff --git a/connectivity/check/test.go b/connectivity/check/test.go index 722f96cc52..00dd635bf3 100644 --- a/connectivity/check/test.go +++ b/connectivity/check/test.go @@ -9,6 +9,7 @@ import ( _ "embed" "fmt" "io" + "net" "sync" "time" @@ -419,12 +420,24 @@ func (t *Test) WithK8SPolicy(policy string) *Test { return t } +const ( + NoExcludedCIDRs = iota + ExternalNodeExcludedCIDRs +) + +// CiliumEgressGatewayPolicyParams is used to configure how a CiliumEgressGatewayPolicy template should be configured +// before being applied. +type CiliumEgressGatewayPolicyParams struct { + // ExcludedCIDRs controls how the ExcludedCIDRs property should be configured + ExcludedCIDRs int +} + // WithCiliumEgressGatewayPolicy takes a string containing a YAML policy // document and adds the cilium egress gateway polic(y)(ies) to the scope of the // Test, to be applied when the test starts running. When calling this method, // note that the egress gateway enabled feature requirement is applied directly // here. -func (t *Test) WithCiliumEgressGatewayPolicy(policy string) *Test { +func (t *Test) WithCiliumEgressGatewayPolicy(policy string, params CiliumEgressGatewayPolicyParams) *Test { pl, err := parseCiliumEgressGatewayPolicyYAML(policy) if err != nil { t.Fatalf("Parsing policy YAML: %s", err) @@ -445,13 +458,28 @@ func (t *Test) WithCiliumEgressGatewayPolicy(policy string) *Test { } } + // Set the egress gateway node egressGatewayNode := t.EgressGatewayNode() if egressGatewayNode == "" { t.Fatalf("Cannot find egress gateway node") } - // Set the egress gateway node pl[i].Spec.EgressGateway.NodeSelector.MatchLabels["kubernetes.io/hostname"] = egressGatewayNode + + // Set the excluded CIDRs + pl[i].Spec.ExcludedCIDRs = []v2.IPv4CIDR{} + + switch params.ExcludedCIDRs { + case ExternalNodeExcludedCIDRs: + for _, nodeWithoutCiliumIP := range t.Context().params.NodesWithoutCiliumIPs { + if parsedIP := net.ParseIP(nodeWithoutCiliumIP.IP); parsedIP.To4() == nil { + continue + } + + cidr := v2.IPv4CIDR(fmt.Sprintf("%s/32", nodeWithoutCiliumIP.IP)) + pl[i].Spec.ExcludedCIDRs = append(pl[i].Spec.ExcludedCIDRs, cidr) + } + } } if err := t.addCEGPs(pl...); err != nil { diff --git a/connectivity/manifests/egress-gateway-policy-excluded-cidrs.yaml b/connectivity/manifests/egress-gateway-policy-excluded-cidrs.yaml new file mode 100644 index 0000000000..3bdff0bff0 --- /dev/null +++ b/connectivity/manifests/egress-gateway-policy-excluded-cidrs.yaml @@ -0,0 +1,18 @@ +apiVersion: cilium.io/v2 +kind: CiliumEgressGatewayPolicy +metadata: + name: cegp-sample-excluded-cidrs +spec: + selectors: + - podSelector: + matchLabels: + io.kubernetes.pod.namespace: cilium-test + kind: client + destinationCIDRs: + - 0.0.0.0/0 + excludedCIDRs: + - NODE_WITHOUT_CILIUM_PLACEHOLDER/32 + egressGateway: + nodeSelector: + matchLabels: + kubernetes.io/hostname: NODE_NAME_PLACEHOLDER diff --git a/connectivity/manifests/egress-gateway-policy.yaml b/connectivity/manifests/egress-gateway-policy.yaml index 392856073e..6cc6f45a14 100644 --- a/connectivity/manifests/egress-gateway-policy.yaml +++ b/connectivity/manifests/egress-gateway-policy.yaml @@ -14,3 +14,19 @@ spec: nodeSelector: matchLabels: kubernetes.io/hostname: NODE_NAME_PLACEHOLDER +--- +apiVersion: cilium.io/v2 +kind: CiliumEgressGatewayPolicy +metadata: + name: cegp-sample-echo-service +spec: + selectors: + - podSelector: + matchLabels: + kind: echo + destinationCIDRs: + - 0.0.0.0/0 + egressGateway: + nodeSelector: + matchLabels: + kubernetes.io/hostname: NODE_NAME_PLACEHOLDER diff --git a/connectivity/suite.go b/connectivity/suite.go index 725253a191..dc338766ae 100644 --- a/connectivity/suite.go +++ b/connectivity/suite.go @@ -11,6 +11,8 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cilium/cilium/pkg/versioncheck" + "github.com/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium-cli/connectivity/manifests/template" "github.com/cilium/cilium-cli/connectivity/tests" @@ -157,6 +159,9 @@ var ( //go:embed manifests/egress-gateway-policy.yaml egressGatewayPolicyYAML string + + //go:embed manifests/egress-gateway-policy-excluded-cidrs.yaml + egressGatewayPolicyExcludedCIDRsYAML string ) var ( @@ -726,13 +731,27 @@ func Run(ctx context.Context, ct *check.ConnectivityTest, addExtraTests func(*ch tests.NodeToNodeEncryption(), ) - ct.NewTest("egress-gateway"). - WithCiliumEgressGatewayPolicy(egressGatewayPolicyYAML). - WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureEgressGateway), - check.RequireFeatureEnabled(check.FeatureNodeWithoutCilium)). - WithScenarios( - tests.EgressGateway(), - ) + if ct.Params().IncludeUnsafeTests { + ct.NewTest("egress-gateway"). + WithCiliumEgressGatewayPolicy(egressGatewayPolicyYAML, check.CiliumEgressGatewayPolicyParams{}). + WithIPRoutesFromOutsideToPodCIDRs(). + WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureEgressGateway), + check.RequireFeatureEnabled(check.FeatureNodeWithoutCilium)). + WithScenarios( + tests.EgressGateway(), + ) + } + + if versioncheck.MustCompile(">=1.14.0")(ct.CiliumVersion) { + ct.NewTest("egress-gateway-excluded-cidrs"). + WithCiliumEgressGatewayPolicy(egressGatewayPolicyExcludedCIDRsYAML, + check.CiliumEgressGatewayPolicyParams{ExcludedCIDRs: check.ExternalNodeExcludedCIDRs}). + WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureEgressGateway), + check.RequireFeatureEnabled(check.FeatureNodeWithoutCilium)). + WithScenarios( + tests.EgressGatewayExcludedCIDRs(), + ) + } // The following tests have DNS redirect policies. They should be executed last. diff --git a/connectivity/tests/egressgateway.go b/connectivity/tests/egressgateway.go index 43e8d3a588..dfe52aff86 100644 --- a/connectivity/tests/egressgateway.go +++ b/connectivity/tests/egressgateway.go @@ -14,84 +14,13 @@ import ( "github.com/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium-cli/defaults" "github.com/cilium/cilium-cli/internal/utils" -) - -// EgressGateway is a test case which, given the cegp-sample -// CiliumEgressGatewayPolicy 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 EgressGateway() check.Scenario { - return &egressGateway{} -} - -type egressGateway struct { - egressGatewayNode string -} - -func (s *egressGateway) Name() string { - return "egress-gateway" -} - -func (s *egressGateway) Run(ctx context.Context, t *check.Test) { - ct := t.Context() - - s.egressGatewayNode = t.EgressGatewayNode() - if s.egressGatewayNode == "" { - t.Fatal("Cannot get egress gateway node") - } - - egressIP := s.getGatewayNodeInternalIP(ct) - - s.waitForBpfPolicyEntries(ctx, t) - - 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(egressIP) { - t.Fatal("Request reached external echo service with wrong source IP") - } - }) - i++ - } - } -} - -// getGatewayNodeInternalIP returns the k8s internal IP of the node acting as -// gateway for this test -func (s *egressGateway) getGatewayNodeInternalIP(ct *check.ConnectivityTest) net.IP { - gatewayNode, ok := ct.Nodes()[s.egressGatewayNode] - if !ok { - return nil - } - - for _, addr := range gatewayNode.Status.Addresses { - if addr.Type != "InternalIP" { - continue - } - - ip := net.ParseIP(addr.Address) - if ip == nil || ip.To4() == nil { - continue - } - - return ip - } - return nil -} + v1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) -// bpfEgressGatewayPolicyEntry represents an entry in the BPF egress gateway -// policy map +// bpfEgressGatewayPolicyEntry represents an entry in the BPF egress gateway policy map type bpfEgressGatewayPolicyEntry struct { SourceIP string DestCIDR string @@ -99,8 +28,7 @@ type bpfEgressGatewayPolicyEntry struct { GatewayIP string } -// matches is an helper used to compare the receiver bpfEgressGatewayPolicyEntry -// with another entry +// matches is an helper used to compare the receiver bpfEgressGatewayPolicyEntry with another entry func (e *bpfEgressGatewayPolicyEntry) matches(t bpfEgressGatewayPolicyEntry) bool { return t.SourceIP == e.SourceIP && t.DestCIDR == e.DestCIDR && @@ -108,36 +36,18 @@ func (e *bpfEgressGatewayPolicyEntry) matches(t bpfEgressGatewayPolicyEntry) boo 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 CiliumEgressGatewayPolicy -func (s *egressGateway) waitForBpfPolicyEntries(ctx context.Context, t *check.Test) { +// waitForBpfPolicyEntries waits for the egress gateway policy maps on each node to be populated with the entries for +// the cegp-sample CiliumEgressGatewayExcludedCIDRsPolicy +func waitForBpfPolicyEntries(ctx context.Context, t *check.Test, + targetEntriesCallback func(ciliumPod check.Pod) []bpfEgressGatewayPolicyEntry) { ct := t.Context() w := utils.NewWaitObserver(ctx, utils.WaitParameters{Timeout: 10 * time.Second}) defer w.Cancel() ensureBpfPolicyEntries := func() error { - gatewayNodeInternalIP := s.getGatewayNodeInternalIP(ct) - if gatewayNodeInternalIP == nil { - t.Fatalf("Cannot retrieve internal IP of gateway node") - } - for _, ciliumPod := range ct.CiliumPods() { - egressIP := "0.0.0.0" - if ciliumPod.Pod.Spec.NodeName == s.egressGatewayNode { - egressIP = gatewayNodeInternalIP.String() - } - - targetEntries := []bpfEgressGatewayPolicyEntry{} - for _, client := range ct.ClientPods() { - targetEntries = append(targetEntries, - bpfEgressGatewayPolicyEntry{ - SourceIP: client.Pod.Status.PodIP, - DestCIDR: "0.0.0.0/0", - EgressIP: egressIP, - GatewayIP: gatewayNodeInternalIP.String(), - }) - } + targetEntries := targetEntriesCallback(ciliumPod) cmd := strings.Split("cilium bpf egress list -o json", " ") stdout, err := ciliumPod.K8sClient.ExecInPod(ctx, ciliumPod.Pod.Namespace, ciliumPod.Pod.Name, defaults.AgentContainerName, cmd) @@ -158,6 +68,17 @@ func (s *egressGateway) waitForBpfPolicyEntries(ctx context.Context, t *check.Te return fmt.Errorf("Could not find egress gateway policy entry matching %+v", targetEntry) } + + nextEntry: + for _, entry := range entries { + for _, targetEntry := range targetEntries { + if targetEntry.matches(entry) { + continue nextEntry + } + } + + return fmt.Errorf("Untracked entry %+v in the egress gateway policy map", entry) + } } return nil @@ -176,8 +97,30 @@ func (s *egressGateway) waitForBpfPolicyEntries(ctx context.Context, t *check.Te } } -// extractClientIPFromResponse extracts the client IP from the response of the -// echo-external service +// getGatewayNodeInternalIP returns the k8s internal IP of the node acting as gateway for this test +func getGatewayNodeInternalIP(ct *check.ConnectivityTest, egressGatewayNode string) net.IP { + gatewayNode, ok := ct.Nodes()[egressGatewayNode] + if !ok { + return nil + } + + for _, addr := range gatewayNode.Status.Addresses { + if addr.Type != v1.NodeInternalIP { + continue + } + + ip := net.ParseIP(addr.Address) + if ip == nil || ip.To4() == nil { + continue + } + + return ip + } + + return nil +} + +// extractClientIPFromResponse extracts the client IP from the response of the echo-external service func extractClientIPFromResponse(res string) net.IP { var clientIP struct { ClientIP string `json:"client-ip"` @@ -187,3 +130,251 @@ func extractClientIPFromResponse(res string) net.IP { return net.ParseIP(clientIP.ClientIP).To4() } + +// EgressGateway is a test case which, given the cegp-sample CiliumEgressGatewayPolicy targeting: +// - a couple of client pods (kind=client) as source +// - the 0.0.0.0/0 destination CIDR +// - kind-worker2 as gateway node +// +// This suite tests connectivity for: +// - pod to host traffic +// - pod to service traffic +// - pod to external IP traffic +// - reply traffic for services +// - reply traffic for pods +func EgressGateway() check.Scenario { + return &egressGateway{} +} + +type egressGateway struct{} + +func (s *egressGateway) Name() string { + return "egress-gateway" +} + +func (s *egressGateway) Run(ctx context.Context, t *check.Test) { + ct := t.Context() + + egressGatewayNode := t.EgressGatewayNode() + if egressGatewayNode == "" { + t.Fatal("Cannot get egress gateway node") + } + + egressGatewayNodeInternalIP := getGatewayNodeInternalIP(ct, egressGatewayNode) + if egressGatewayNodeInternalIP == nil { + t.Fatal("Cannot get egress gateway node internal IP") + } + + waitForBpfPolicyEntries(ctx, t, func(ciliumPod check.Pod) []bpfEgressGatewayPolicyEntry { + targetEntries := []bpfEgressGatewayPolicyEntry{} + + egressIP := "0.0.0.0" + if ciliumPod.Pod.Spec.NodeName == egressGatewayNode { + egressIP = egressGatewayNodeInternalIP.String() + } + + for _, client := range ct.ClientPods() { + targetEntries = append(targetEntries, + bpfEgressGatewayPolicyEntry{ + SourceIP: client.Pod.Status.PodIP, + DestCIDR: "0.0.0.0/0", + EgressIP: egressIP, + GatewayIP: egressGatewayNodeInternalIP.String(), + }) + } + + for _, echo := range ct.EchoPods() { + targetEntries = append(targetEntries, + bpfEgressGatewayPolicyEntry{ + SourceIP: echo.Pod.Status.PodIP, + DestCIDR: "0.0.0.0/0", + EgressIP: egressIP, + GatewayIP: egressGatewayNodeInternalIP.String(), + }) + } + + return targetEntries + }) + + // Ping hosts (pod to host connectivity). Should not get masqueraded with egress IP + i := 0 + for _, client := range ct.ClientPods() { + client := client + + for _, dst := range ct.HostNetNSPodsByNode() { + dst := dst + + t.NewAction(s, fmt.Sprintf("ping-%d", i), &client, &dst, check.IPFamilyV4).Run(func(a *check.Action) { + a.ExecInPod(ctx, ct.PingCommand(dst, check.IPFamilyV4)) + }) + i++ + } + } + + // DNS query (pod to service connectivity). Should not get masqueraded with egress IP + i = 0 + for _, client := range ct.ClientPods() { + client := client + + kubeDNSService, err := ct.K8sClient().GetService(ctx, "kube-system", "kube-dns", metav1.GetOptions{}) + if err != nil { + t.Fatal("Cannot get kube-dns service") + } + kubeDNSServicePeer := check.Service{Service: kubeDNSService} + + t.NewAction(s, fmt.Sprintf("dig-%d", i), &client, kubeDNSServicePeer, check.IPFamilyV4).Run(func(a *check.Action) { + a.ExecInPod(ctx, ct.DigCommand(kubeDNSServicePeer, check.IPFamilyV4)) + }) + i++ + } + + // 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-external-echo-pod-%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(egressGatewayNodeInternalIP) { + t.Fatal("Request reached external echo service with wrong source IP") + } + }) + i++ + } + } + + // When connecting from outside the cluster to a nodeport service whose pods are selected by an egress policy, + // the reply traffic should not be SNATed with the egress IP + i = 0 + for _, client := range ct.ExternalEchoPods() { + client := client + + for _, node := range ct.Nodes() { + for _, echo := range ct.EchoServices() { + // convert the service to a ServiceExternalIP as we want to access it through its external IP + echo := echo.ToNodeportService(node) + + t.NewAction(s, fmt.Sprintf("curl-echo-service-%d", i), &client, echo, check.IPFamilyV4).Run(func(a *check.Action) { + a.ExecInPod(ctx, ct.CurlCommand(echo, check.IPFamilyV4)) + }) + i++ + } + } + } + + if status, ok := ct.Feature(check.FeatureTunnel); ok && !status.Enabled { + // When connecting from outside the cluster directly to a pod which is selected by an egress policy, the + // reply traffic should not be SNATed with the egress IP (only connections originating from these pods + // should go through egress gateway). + // + // This test is executed only when Cilium is running in direct routing mode, since we can simply add a + // route on the node that doesn't run Cilium to direct pod's traffic to the node where the pod is + // running (while in tunneling mode we would need the external node to send the traffic over the tunnel) + i = 0 + for _, client := range ct.ExternalEchoPods() { + client := client + + for _, echo := range ct.EchoPods() { + t.NewAction(s, fmt.Sprintf("curl-echo-pod-%d", i), &client, echo, check.IPFamilyV4).Run(func(a *check.Action) { + a.ExecInPod(ctx, ct.CurlCommand(echo, check.IPFamilyV4)) + }) + i++ + } + } + } +} + +// EgressGatewayExcludedCIDRs is a test case which, given the cegp-sample-excluded-cidrs CiliumEgressGatewayPolicy +// targeting: +// - a couple of client pods (kind=client) as source +// - the 0.0.0.0/0 destination CIDR +// - the IP of the external node as excluded CIDR +// - kind-worker2 as gateway node +// +// This suite tests tests the excludedCIDRs property and ensure traffic matching +// an excluded CIDR does not get masqueraded with the egress IP. +func EgressGatewayExcludedCIDRs() check.Scenario { + return &egressGatewayExcludedCIDRs{} +} + +type egressGatewayExcludedCIDRs struct{} + +func (s *egressGatewayExcludedCIDRs) Name() string { + return "egress-gateway-excluded-cidrs" +} + +func (s *egressGatewayExcludedCIDRs) Run(ctx context.Context, t *check.Test) { + ct := t.Context() + + egressGatewayNode := t.EgressGatewayNode() + if egressGatewayNode == "" { + t.Fatal("Cannot get egress gateway node") + } + + egressGatewayNodeInternalIP := getGatewayNodeInternalIP(ct, egressGatewayNode) + if egressGatewayNodeInternalIP == nil { + t.Fatal("Cannot get egress gateway node internal IP") + } + + waitForBpfPolicyEntries(ctx, t, func(ciliumPod check.Pod) []bpfEgressGatewayPolicyEntry { + targetEntries := []bpfEgressGatewayPolicyEntry{} + + egressIP := "0.0.0.0" + if ciliumPod.Pod.Spec.NodeName == egressGatewayNode { + egressIP = egressGatewayNodeInternalIP.String() + } + + for _, client := range ct.ClientPods() { + for _, nodeWithoutCiliumName := range t.NodesWithoutCilium() { + nodeWithoutCilium, err := ciliumPod.K8sClient.GetNode(context.Background(), nodeWithoutCiliumName, metav1.GetOptions{}) + if err != nil { + if k8sErrors.IsNotFound(err) { + continue + } + + t.Fatalf("Cannot retrieve external node") + } + + targetEntries = append(targetEntries, + bpfEgressGatewayPolicyEntry{ + SourceIP: client.Pod.Status.PodIP, + DestCIDR: "0.0.0.0/0", + EgressIP: egressIP, + GatewayIP: egressGatewayNodeInternalIP.String(), + }) + + targetEntries = append(targetEntries, + bpfEgressGatewayPolicyEntry{ + SourceIP: client.Pod.Status.PodIP, + DestCIDR: fmt.Sprintf("%s/32", nodeWithoutCilium.Status.Addresses[0].Address), + EgressIP: egressIP, + GatewayIP: "Excluded CIDR", + }) + } + } + + return targetEntries + }) + + // Traffic matching an egress gateway policy and an excluded CIDR should leave the cluster masqueraded with the + // node IP where the pod is running rather than 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++ + } + } +} diff --git a/k8s/client.go b/k8s/client.go index 98b0141562..0d3d94b641 100644 --- a/k8s/client.go +++ b/k8s/client.go @@ -669,6 +669,10 @@ func (c *Client) ListCiliumEnvoyConfigs(ctx context.Context, namespace string, o return c.CiliumClientset.CiliumV2().CiliumEnvoyConfigs(namespace).List(ctx, options) } +func (c *Client) GetNode(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Node, error) { + return c.Clientset.CoreV1().Nodes().Get(ctx, name, opts) +} + func (c *Client) ListNodes(ctx context.Context, options metav1.ListOptions) (*corev1.NodeList, error) { return c.Clientset.CoreV1().Nodes().List(ctx, options) }