From de5fab1fbc810c8beab3e6fe0ecfb4b215f518b6 Mon Sep 17 00:00:00 2001 From: Divyam pateriya Date: Thu, 28 May 2026 22:30:32 +0530 Subject: [PATCH] fix: OCPBUGS-86693: add machineNetwork CIDRs to KAS NO_PROXY to prevent x509 errors When the management cluster uses an HTTP proxy, the kube-apiserver (KAS) container in the hosted control plane inherits the management cluster proxy environment. The NO_PROXY list for KAS already includes the guest cluster's pod network (clusterNetwork) and service network (serviceNetwork) CIDRs, but omits the machine network (machineNetwork) CIDRs. Because KAS connects to kubelets on node IPs (in the machineNetwork range) via the konnectivity tunnel for operations like oc logs, oc exec, and oc attach, these HTTPS connections are routed through the management cluster's HTTP proxy. The proxy performs TLS interception, presenting its own certificate which is not trusted by KAS, resulting in: x509: certificate signed by unknown authority Adding the machineNetwork CIDRs to the NO_PROXY list ensures these connections bypass the proxy and go directly through konnectivity. Co-authored-by: Cursor --- .../hostedcontrolplane/v2/kas/deployment.go | 8 +- .../v2/kas/deployment_test.go | 81 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go index cedcc84af82..ceb03238ac4 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go +++ b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go @@ -179,13 +179,17 @@ func updateMainContainer(podSpec *corev1.PodSpec, hcp *hyperv1.HostedControlPlan fmt.Sprintf("--v=%d", kasVerbosityLevel), ) - // We have to exempt the pod and service CIDR, otherwise the proxy will get respected by the transport inside - // the the egress transport and that breaks the egress selection/konnektivity usage. + // We have to exempt the pod, service, and machine CIDRs, otherwise the proxy will get respected by the + // transport inside the egress transport and that breaks the egress selection/konnectivity usage. + // The machine network must be included because KAS connects to kubelets on node IPs + // (for oc logs/exec/attach) via konnectivity; without it the management cluster proxy + // intercepts these HTTPS connections causing TLS failures. // Using a CIDR is not supported by Go's default ProxyFunc, but Kube uses a custom one by default that does support it: // https://github.com/kubernetes/kubernetes/blob/ab13c85316015cf9f115e29923ba9740bd1564fd/staging/src/k8s.io/apimachinery/pkg/util/net/http.go#L112-L114 var additionalNoProxyCIDRS []string additionalNoProxyCIDRS = append(additionalNoProxyCIDRS, netutil.ClusterCIDRs(hcp.Spec.Networking.ClusterNetwork)...) additionalNoProxyCIDRS = append(additionalNoProxyCIDRS, netutil.ServiceCIDRs(hcp.Spec.Networking.ServiceNetwork)...) + additionalNoProxyCIDRS = append(additionalNoProxyCIDRS, netutil.MachineCIDRs(hcp.Spec.Networking.MachineNetwork)...) proxy.SetEnvVars(&c.Env, additionalNoProxyCIDRS...) if hcp.Annotations[hyperv1.KubeAPIServerGOGCAnnotation] != "" { diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go index a00ee0b3dfd..b7102dc92f4 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go +++ b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go @@ -6,9 +6,90 @@ import ( . "github.com/onsi/gomega" + hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1" + "github.com/openshift/hypershift/api/util/ipnet" + corev1 "k8s.io/api/core/v1" ) +func TestUpdateMainContainerNoProxy(t *testing.T) { + testCases := []struct { + name string + clusterNetwork []hyperv1.ClusterNetworkEntry + serviceNetwork []hyperv1.ServiceNetworkEntry + machineNetwork []hyperv1.MachineNetworkEntry + expectedCIDRs []string + }{ + { + name: "When proxy is configured with machineNetwork it should include cluster service machine and kube-apiserver in NO_PROXY", + clusterNetwork: []hyperv1.ClusterNetworkEntry{{CIDR: *ipnet.MustParseCIDR("10.128.0.0/14")}}, + serviceNetwork: []hyperv1.ServiceNetworkEntry{{CIDR: *ipnet.MustParseCIDR("172.30.0.0/16")}}, + machineNetwork: []hyperv1.MachineNetworkEntry{{CIDR: *ipnet.MustParseCIDR("192.168.1.0/24")}}, + expectedCIDRs: []string{"10.128.0.0/14", "172.30.0.0/16", "192.168.1.0/24", "kube-apiserver"}, + }, + { + name: "When proxy is configured without machineNetwork it should include cluster and service in NO_PROXY", + clusterNetwork: []hyperv1.ClusterNetworkEntry{{CIDR: *ipnet.MustParseCIDR("10.128.0.0/14")}}, + serviceNetwork: []hyperv1.ServiceNetworkEntry{{CIDR: *ipnet.MustParseCIDR("172.30.0.0/16")}}, + expectedCIDRs: []string{"10.128.0.0/14", "172.30.0.0/16", "kube-apiserver"}, + }, + { + name: "When proxy is configured with dual-stack machineNetwork it should include both IPv4 and IPv6 CIDRs in NO_PROXY", + clusterNetwork: []hyperv1.ClusterNetworkEntry{{CIDR: *ipnet.MustParseCIDR("10.128.0.0/14")}}, + serviceNetwork: []hyperv1.ServiceNetworkEntry{{CIDR: *ipnet.MustParseCIDR("172.30.0.0/16")}}, + machineNetwork: []hyperv1.MachineNetworkEntry{ + {CIDR: *ipnet.MustParseCIDR("192.168.1.0/24")}, + {CIDR: *ipnet.MustParseCIDR("fd00::/48")}, + }, + expectedCIDRs: []string{"10.128.0.0/14", "172.30.0.0/16", "192.168.1.0/24", "fd00::/48", "kube-apiserver"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Setenv("HTTP_PROXY", "http://proxy.example.com:3128") + t.Setenv("HTTPS_PROXY", "http://proxy.example.com:3128") + t.Setenv("NO_PROXY", "") + + hcp := &hyperv1.HostedControlPlane{ + Spec: hyperv1.HostedControlPlaneSpec{ + Networking: hyperv1.ClusterNetworking{ + ClusterNetwork: tc.clusterNetwork, + ServiceNetwork: tc.serviceNetwork, + MachineNetwork: tc.machineNetwork, + }, + }, + } + + podSpec := &corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: ComponentName, + Ports: []corev1.ContainerPort{ + {ContainerPort: 6443}, + }, + }, + }, + } + + updateMainContainer(podSpec, hcp) + + g := NewWithT(t) + var noProxyValue string + for _, env := range podSpec.Containers[0].Env { + if env.Name == "NO_PROXY" { + noProxyValue = env.Value + break + } + } + g.Expect(noProxyValue).ToNot(BeEmpty(), "NO_PROXY env var should be set when proxy is configured") + for _, cidr := range tc.expectedCIDRs { + g.Expect(noProxyValue).To(ContainSubstring(cidr), "NO_PROXY should contain %s", cidr) + } + }) + } +} + func TestAddImagePrePullInitContainers(t *testing.T) { testCases := []struct { name string