From c91d2dedf101666730a7546b073be68b7699667a Mon Sep 17 00:00:00 2001 From: David Hurta Date: Fri, 5 Jun 2026 14:51:17 +0200 Subject: [PATCH 1/4] feat(cvo): add TLS cipher suites and minimum version flags Add new flags to the hosted CVO to comply with the centralized TLS configuration in HyperShift. These flags are to override the CVO's internal TLS profile, which is used for its metrics server. --- ...aneComponents_cluster_version_operator_deployment.yaml | 2 ++ ...aneComponents_cluster_version_operator_deployment.yaml | 2 ++ ...aneComponents_cluster_version_operator_deployment.yaml | 2 ++ ...aneComponents_cluster_version_operator_deployment.yaml | 2 ++ ...aneComponents_cluster_version_operator_deployment.yaml | 2 ++ .../controllers/hostedcontrolplane/v2/cvo/deployment.go | 8 ++++++++ 6 files changed, 18 insertions(+) diff --git a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/AROSwift/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/AROSwift/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml index 2d9b1f3026d5..101273662268 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/AROSwift/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml +++ b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/AROSwift/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml @@ -76,6 +76,8 @@ spec: - --serving-key-file=/etc/kubernetes/certs/server/tls.key - --hypershift=true - --v=4 + - --tls-min-version=VersionTLS12 + - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 command: - cluster-version-operator env: diff --git a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/GCP/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/GCP/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml index 1b3c7f44f78c..f0369c443e3a 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/GCP/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml +++ b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/GCP/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml @@ -76,6 +76,8 @@ spec: - --serving-key-file=/etc/kubernetes/certs/server/tls.key - --hypershift=true - --v=4 + - --tls-min-version=VersionTLS12 + - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 command: - cluster-version-operator env: diff --git a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/IBMCloud/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/IBMCloud/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml index 553aad2bbe4e..535df46592ca 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/IBMCloud/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml +++ b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/IBMCloud/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml @@ -76,6 +76,8 @@ spec: - --serving-key-file=/etc/kubernetes/certs/server/tls.key - --hypershift=true - --v=4 + - --tls-min-version=VersionTLS12 + - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 command: - cluster-version-operator env: diff --git a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/TechPreviewNoUpgrade/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/TechPreviewNoUpgrade/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml index e1230b097c4a..585e20f18edd 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/TechPreviewNoUpgrade/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml +++ b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/TechPreviewNoUpgrade/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml @@ -76,6 +76,8 @@ spec: - --serving-key-file=/etc/kubernetes/certs/server/tls.key - --hypershift=true - --v=4 + - --tls-min-version=VersionTLS12 + - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 command: - cluster-version-operator env: diff --git a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml index 2d9b1f3026d5..101273662268 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml +++ b/control-plane-operator/controllers/hostedcontrolplane/testdata/cluster-version-operator/zz_fixture_TestControlPlaneComponents_cluster_version_operator_deployment.yaml @@ -76,6 +76,8 @@ spec: - --serving-key-file=/etc/kubernetes/certs/server/tls.key - --hypershift=true - --v=4 + - --tls-min-version=VersionTLS12 + - --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 command: - cluster-version-operator env: diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go index 0e337423a6a6..4163f1becae2 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go +++ b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go @@ -102,12 +102,20 @@ func (cvo *clusterVersionOperator) adaptDeployment(cpContext component.WorkloadC }) }) + configuration := cpContext.HCP.Spec.Configuration podspec.UpdateContainer(ComponentName, deployment.Spec.Template.Spec.Containers, func(c *corev1.Container) { podspec.UpsertEnvVar(c, corev1.EnvVar{ Name: "RELEASE_IMAGE", Value: dataPlaneReleaseImage, }) + if tlsMinVersion := config.MinTLSVersion(configuration.GetTLSSecurityProfile()); tlsMinVersion != "" { + c.Args = append(c.Args, fmt.Sprintf("--tls-min-version=%s", tlsMinVersion)) + } + if cipherSuites := config.CipherSuites(configuration.GetTLSSecurityProfile()); len(cipherSuites) != 0 { + c.Args = append(c.Args, fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(cipherSuites, ","))) + } + if updateService := cpContext.HCP.Spec.UpdateService; updateService != "" { c.Args = append(c.Args, "--update-service", string(updateService)) } From b07e7035ddc38bf40e1374e8cf2d61846cff48c6 Mon Sep 17 00:00:00 2001 From: David Hurta Date: Wed, 1 Jul 2026 02:24:18 +0200 Subject: [PATCH 2/4] refactor(cvo): simplify setting TLS profile To keep lines concise and reuse the returned value from the function. --- .../controllers/hostedcontrolplane/v2/cvo/deployment.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go index 4163f1becae2..635d8c91daff 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go +++ b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment.go @@ -109,10 +109,11 @@ func (cvo *clusterVersionOperator) adaptDeployment(cpContext component.WorkloadC Value: dataPlaneReleaseImage, }) - if tlsMinVersion := config.MinTLSVersion(configuration.GetTLSSecurityProfile()); tlsMinVersion != "" { + tlsProfile := configuration.GetTLSSecurityProfile() + if tlsMinVersion := config.MinTLSVersion(tlsProfile); tlsMinVersion != "" { c.Args = append(c.Args, fmt.Sprintf("--tls-min-version=%s", tlsMinVersion)) } - if cipherSuites := config.CipherSuites(configuration.GetTLSSecurityProfile()); len(cipherSuites) != 0 { + if cipherSuites := config.CipherSuites(tlsProfile); len(cipherSuites) != 0 { c.Args = append(c.Args, fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(cipherSuites, ","))) } From d3064dbe60a139fa9780a2a5215599faf91ce6b9 Mon Sep 17 00:00:00 2001 From: David Hurta Date: Wed, 1 Jul 2026 02:53:14 +0200 Subject: [PATCH 3/4] test(cvo): test TLS profile flag combinations Test adaptDeployment with various combinations of set and unset cipher suites and MinTLSVersion flags. Co-authored-by: Claude Code --- .../v2/cvo/deployment_test.go | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go index 7542a505ab73..26b10dff4038 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go +++ b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go @@ -1,16 +1,27 @@ package cvo import ( + "context" "strings" "testing" . "github.com/onsi/gomega" hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1" + "github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/common" + "github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/v2/assets" + "github.com/openshift/hypershift/support/api" + component "github.com/openshift/hypershift/support/controlplane-component" + "github.com/openshift/hypershift/support/podspec" + "github.com/openshift/hypershift/support/util/fakeimagemetadataprovider" configv1 "github.com/openshift/api/config/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) func TestPreparePayloadScript(t *testing.T) { @@ -198,3 +209,196 @@ func extractResourceNames(objects []client.Object) []string { } return names } + +func createTestContext(hcp *hyperv1.HostedControlPlane) component.WorkloadContext { + pullSecret := common.PullSecret(hcp.Namespace) + pullSecret.Data = map[string][]byte{ + corev1.DockerConfigJsonKey: []byte(`{"auths":{"test.registry":{"auth":"dGVzdDp0ZXN0"}}}`), + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(api.Scheme). + WithObjects(pullSecret). + Build() + + fakeImageProvider := &fakeimagemetadataprovider.FakeRegistryClientImageMetadataProvider{ + Digest: "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + } + + if hcp.Spec.ReleaseImage == "" { + hcp.Spec.ReleaseImage = "quay.io/openshift-release-dev/ocp-release:4.18.0-x86_64" + } + + return component.WorkloadContext{ + Context: context.Background(), + Client: fakeClient, + HCP: hcp, + ImageMetadataProvider: fakeImageProvider, + } +} + +func TestAdaptDeployment(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + hcp *hyperv1.HostedControlPlane + expectedTLSMinVersion string + expectedCipherSuites string + }{ + { + name: "When TLS profile has empty cipher suites list, it should not add tls-cipher-suites flag", + hcp: &hyperv1.HostedControlPlane{ + ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "test-ns"}, + Spec: hyperv1.HostedControlPlaneSpec{ + Platform: hyperv1.PlatformSpec{Type: hyperv1.AWSPlatform}, + Configuration: &hyperv1.ClusterConfiguration{ + APIServer: &configv1.APIServerSpec{ + TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileCustomType, + Custom: &configv1.CustomTLSProfile{ + TLSProfileSpec: configv1.TLSProfileSpec{ + Ciphers: []string{}, + MinTLSVersion: configv1.VersionTLS12, + }, + }, + }, + }, + }, + }, + }, + expectedTLSMinVersion: "--tls-min-version=VersionTLS12", + expectedCipherSuites: "", + }, + { + name: "When TLS profile has empty MinTLSVersion, it should not add tls-min-version flag", + hcp: &hyperv1.HostedControlPlane{ + ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "test-ns"}, + Spec: hyperv1.HostedControlPlaneSpec{ + Platform: hyperv1.PlatformSpec{Type: hyperv1.AWSPlatform}, + Configuration: &hyperv1.ClusterConfiguration{ + APIServer: &configv1.APIServerSpec{ + TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileCustomType, + Custom: &configv1.CustomTLSProfile{ + TLSProfileSpec: configv1.TLSProfileSpec{ + Ciphers: []string{"ECDHE-RSA-AES128-GCM-SHA256"}, + MinTLSVersion: "", + }, + }, + }, + }, + }, + }, + }, + expectedTLSMinVersion: "", + expectedCipherSuites: "--tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + }, + { + name: "When TLS profile has both fields empty, it should not add any TLS flags", + hcp: &hyperv1.HostedControlPlane{ + ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "test-ns"}, + Spec: hyperv1.HostedControlPlaneSpec{ + Platform: hyperv1.PlatformSpec{Type: hyperv1.AWSPlatform}, + Configuration: &hyperv1.ClusterConfiguration{ + APIServer: &configv1.APIServerSpec{ + TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileCustomType, + Custom: &configv1.CustomTLSProfile{ + TLSProfileSpec: configv1.TLSProfileSpec{ + Ciphers: []string{}, + MinTLSVersion: "", + }, + }, + }, + }, + }, + }, + }, + expectedTLSMinVersion: "", + expectedCipherSuites: "", + }, + { + name: "When TLS profile has both fields set and a single cipher suite, it should add both flags and a single cipher", + hcp: &hyperv1.HostedControlPlane{ + ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "test-ns"}, + Spec: hyperv1.HostedControlPlaneSpec{ + Platform: hyperv1.PlatformSpec{Type: hyperv1.AWSPlatform}, + Configuration: &hyperv1.ClusterConfiguration{ + APIServer: &configv1.APIServerSpec{ + TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileCustomType, + Custom: &configv1.CustomTLSProfile{ + TLSProfileSpec: configv1.TLSProfileSpec{ + Ciphers: []string{"ECDHE-RSA-AES128-GCM-SHA256"}, + MinTLSVersion: configv1.VersionTLS12, + }, + }, + }, + }, + }, + }, + }, + expectedTLSMinVersion: "--tls-min-version=VersionTLS12", + expectedCipherSuites: "--tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + }, + { + name: "When TLS profile has both fields set and multiple cipher suites, it should add both flags and a comma-separated cipher list", + hcp: &hyperv1.HostedControlPlane{ + ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "test-ns"}, + Spec: hyperv1.HostedControlPlaneSpec{ + Platform: hyperv1.PlatformSpec{Type: hyperv1.AWSPlatform}, + Configuration: &hyperv1.ClusterConfiguration{ + APIServer: &configv1.APIServerSpec{ + TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileCustomType, + Custom: &configv1.CustomTLSProfile{ + TLSProfileSpec: configv1.TLSProfileSpec{ + Ciphers: []string{ + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES256-GCM-SHA384", + }, + MinTLSVersion: configv1.VersionTLS13, + }, + }, + }, + }, + }, + }, + }, + expectedTLSMinVersion: "--tls-min-version=VersionTLS13", + expectedCipherSuites: "--tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + deployment, err := assets.LoadDeploymentManifest(ComponentName) + g.Expect(err).ToNot(HaveOccurred()) + + cpContext := createTestContext(tc.hcp) + + cvo := &clusterVersionOperator{} + err = cvo.adaptDeployment(cpContext, deployment) + g.Expect(err).ToNot(HaveOccurred()) + + container := podspec.FindContainer(ComponentName, deployment.Spec.Template.Spec.Containers) + g.Expect(container).ToNot(BeNil()) + + if tc.expectedTLSMinVersion != "" { + g.Expect(container.Args).To(ContainElement(tc.expectedTLSMinVersion)) + } else { + g.Expect(container.Args).ToNot(ContainElement(ContainSubstring("--tls-min-version"))) + } + + if tc.expectedCipherSuites != "" { + g.Expect(container.Args).To(ContainElement(tc.expectedCipherSuites)) + } else { + g.Expect(container.Args).ToNot(ContainElement(ContainSubstring("--tls-cipher-suites"))) + } + }) + } +} From b820f5a43a221df21bc57c778605d55b442cd065 Mon Sep 17 00:00:00 2001 From: David Hurta Date: Sun, 5 Jul 2026 23:23:06 +0200 Subject: [PATCH 4/4] test(cvo): add Modern TLS profile test case Add test case that verifies Modern TLS profile behavior: sets TLS 1.3 as minimum version without adding cipher suites flag. This exercises the configv1.TLSProfiles[securityProfile.Type].Ciphers lookup path and confirms that Modern profile's TLS 1.3 ciphers (commented out in openSSLToIANACiphersMap) result in no --tls-cipher-suites argument. Co-authored-by: Claude Code --- .../v2/cvo/deployment_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go index 26b10dff4038..2309126eed3c 100644 --- a/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go +++ b/control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go @@ -369,6 +369,25 @@ func TestAdaptDeployment(t *testing.T) { expectedTLSMinVersion: "--tls-min-version=VersionTLS13", expectedCipherSuites: "--tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", }, + { + name: "When TLS profile type is Modern, it should set min version to TLS 1.3 and not add cipher suites flag", + hcp: &hyperv1.HostedControlPlane{ + ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "test-ns"}, + Spec: hyperv1.HostedControlPlaneSpec{ + Platform: hyperv1.PlatformSpec{Type: hyperv1.AWSPlatform}, + Configuration: &hyperv1.ClusterConfiguration{ + APIServer: &configv1.APIServerSpec{ + TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileModernType, + Modern: &configv1.ModernTLSProfile{}, + }, + }, + }, + }, + }, + expectedTLSMinVersion: "--tls-min-version=VersionTLS13", + expectedCipherSuites: "", + }, } for _, tc := range testCases {