From 06df1b799b1fb9897b51e2da9731a7dba04d41b1 Mon Sep 17 00:00:00 2001 From: Josh Siviour Date: Thu, 3 Sep 2026 16:56:52 +1000 Subject: [PATCH] fix: tunnel apiserver controlplane egress through konnectivity (#991) The EgressSelectorConfiguration written to every EKS control-plane VM defined only the `cluster` egress selection, which carries kubelet exec/logs/portforward. Admission webhooks and aggregated apiservers dial via the `controlplane` selection instead, and an undefined selection falls back to a direct dial. The control-plane VM sits alone in its own VPC with no route to the service CIDR, so every webhook call timed out after 10s and konnectivity never saw a single dial request for one. Define both selections on the konnectivity-server UDS so webhook traffic rides the agent-initiated reverse tunnel alongside kubelet traffic. --- spinifex/handlers/eks/k3s_server_vm.go | 32 +++++++++++++-------- spinifex/handlers/eks/k3s_server_vm_test.go | 14 +++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/spinifex/handlers/eks/k3s_server_vm.go b/spinifex/handlers/eks/k3s_server_vm.go index 9d286ac09..86130d3a5 100644 --- a/spinifex/handlers/eks/k3s_server_vm.go +++ b/spinifex/handlers/eks/k3s_server_vm.go @@ -87,9 +87,9 @@ const ( k3sConfigPath = "/etc/rancher/k3s/config.yaml" // k3sEgressSelectorConfigPath is the EgressSelectorConfiguration the apiserver - // loads via --egress-selector-config-file. It points the `cluster` egress at the - // konnectivity-server UDS, so apiserver→pod/kubelet traffic (exec/logs/webhooks) - // rides the agent-initiated reverse tunnel instead of a direct (unroutable) IP. + // loads via --egress-selector-config-file. It points the `controlplane` and + // `cluster` egress at the konnectivity-server UDS, so apiserver→pod/kubelet + // traffic rides the agent-initiated reverse tunnel, not a direct IP. k3sEgressSelectorConfigPath = "/etc/rancher/k3s/egress-selector-config.yaml" // konnectivityUDSPath is the unix socket the konnectivity-server listens on for @@ -469,20 +469,28 @@ func k3sServerJoinURL(ip string) string { } // egressSelectorConfigYAML renders the EgressSelectorConfiguration that wires the -// apiserver `cluster` egress to the konnectivity-server UDS. v1beta1 is the schema +// apiserver egress to the konnectivity-server UDS. v1beta1 is the schema // k3s/kube-apiserver accept for --egress-selector-config-file. func egressSelectorConfigYAML() string { - return strings.Join([]string{ + lines := []string{ "apiVersion: apiserver.k8s.io/v1beta1", "kind: EgressSelectorConfiguration", "egressSelections:", - " - name: cluster", - " connection:", - " proxyProtocol: GRPC", - " transport:", - " uds:", - " udsName: " + konnectivityUDSPath, - }, "\n") + } + // controlplane carries admission-webhook and aggregated-apiserver dials; + // cluster carries kubelet exec/logs/portforward. Both must ride the tunnel: + // an undefined selection falls back to a direct, unroutable dial from the CP. + for _, name := range []string{"controlplane", "cluster"} { + lines = append(lines, + " - name: "+name, + " connection:", + " proxyProtocol: GRPC", + " transport:", + " uds:", + " udsName: "+konnectivityUDSPath, + ) + } + return strings.Join(lines, "\n") } // dedupeNonEmpty returns the input with empty strings dropped and duplicates diff --git a/spinifex/handlers/eks/k3s_server_vm_test.go b/spinifex/handlers/eks/k3s_server_vm_test.go index 643926444..0914c65a7 100644 --- a/spinifex/handlers/eks/k3s_server_vm_test.go +++ b/spinifex/handlers/eks/k3s_server_vm_test.go @@ -440,6 +440,20 @@ func TestBuildK3sUserData_EgressSelectorDisabledWithKonnConfig(t *testing.T) { "the EgressSelectorConfiguration file routes the cluster egress to the konn socket") } +func TestEgressSelectorConfigYAML_TunnelsControlPlaneAndCluster(t *testing.T) { + cfg := egressSelectorConfigYAML() + + // Admission webhooks and aggregated apiservers dial via `controlplane`; only + // kubelet exec/logs/portforward use `cluster`. Omitting controlplane leaves + // webhook dials direct, which cannot route from the CP VPC to the service CIDR. + assert.Contains(t, cfg, " - name: controlplane", + "webhook dials use the controlplane egress and must ride the konnectivity tunnel") + assert.Contains(t, cfg, " - name: cluster", + "kubelet exec/logs dials use the cluster egress") + assert.Equal(t, 2, strings.Count(cfg, "udsName: "+konnectivityUDSPath), + "both egress selections must terminate on the konnectivity socket") +} + func TestBuildK3sUserData_KonnectivityEnv(t *testing.T) { in := validK3sInput() in.PrivateEndpointIP = "10.32.100.4"