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