From 53091fbd62e0742eeb54ad5129a0f7c0b5ac4773 Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Thu, 8 Oct 2026 13:23:04 +0530 Subject: [PATCH 1/7] feat(tls): configure TLS curve preferences for gitops-plugin Signed-off-by: akhil nittala --- argocd-operator/pkg/tlsprofile/profile.go | 2 + cmd/main.go | 4 ++ config/manager/kustomization.yaml | 3 +- controllers/consoleplugin.go | 32 ++++++++++ controllers/consoleplugin_test.go | 75 +++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 7 files changed, 118 insertions(+), 1 deletion(-) diff --git a/argocd-operator/pkg/tlsprofile/profile.go b/argocd-operator/pkg/tlsprofile/profile.go index 6b83c09b2fe..71d50b676a7 100644 --- a/argocd-operator/pkg/tlsprofile/profile.go +++ b/argocd-operator/pkg/tlsprofile/profile.go @@ -8,4 +8,6 @@ type TLSConfigProfile struct { MinVersion configv1.TLSProtocolVersion // Ciphers specifies the list of supported TLS cipher suites in cluster. Ciphers []string + // CurvePreferences specifies the list of supported TLS curve preferences in cluster. + CurvePreferences []string } diff --git a/cmd/main.go b/cmd/main.go index a4a8a72bdce..6e10c3a15f5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -51,6 +51,7 @@ import ( operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/samber/lo" corev1 "k8s.io/api/core/v1" crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/labels" @@ -382,6 +383,9 @@ func main() { DisableClusterTLSProfile: disableClusterTLSProfile, MinVersion: profile.MinTLSVersion, Ciphers: profile.Ciphers, + CurvePreferences: lo.Map(profile.Groups, func(group configv1.TLSGroup, _ int) string { + return string(group) + }), }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Argo CD") diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index c7ac940f7d1..2df5e5f30be 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -12,4 +12,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: quay.io/redhat-developer/gitops-operator + newName: quay.io/nittalaakhil/openshift-gitops-operator + newTag: v5.0.0 diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go index aab97f443a0..bac312c146b 100644 --- a/controllers/consoleplugin.go +++ b/controllers/consoleplugin.go @@ -13,6 +13,7 @@ import ( argocommon "github.com/argoproj-labs/gitops-operator/argocd-operator/common" argocdutil "github.com/argoproj-labs/gitops-operator/argocd-operator/controllers/argoutil" + configv1 "github.com/openshift/api/config/v1" consolev1 "github.com/openshift/api/console/v1" pipelinesv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" "github.com/redhat-developer/gitops-operator/controllers/util" @@ -250,6 +251,34 @@ func securityContextForPlugin() *corev1.SecurityContext { } } +// allowedHttpdGroups lists TLS groups supported by OpenSSL via SSLOpenSSLConfCmd Groups. +// OpenShift TLSGroup string values are directly usable as OpenSSL group names (OpenSSL >= 3.5). +var allowedHttpdGroups = map[configv1.TLSGroup]struct{}{ + configv1.TLSGroupX25519: {}, + configv1.TLSGroupSecP256r1: {}, + configv1.TLSGroupSecP384r1: {}, + configv1.TLSGroupSecP521r1: {}, + configv1.TLSGroupX25519MLKEM768: {}, + configv1.TLSGroupSecP256r1MLKEM768: {}, + configv1.TLSGroupSecP384r1MLKEM1024: {}, +} + +// tlsGroupsForHttpd converts OpenShift TLS groups to a colon-separated OpenSSL +// Groups string for Apache's SSLOpenSSLConfCmd directive. Unknown groups are +// filtered out so forward-compatible profile values do not break httpd. +func tlsGroupsForHttpd(groups []configv1.TLSGroup) string { + if len(groups) == 0 { + return "" + } + names := make([]string, 0, len(groups)) + for _, g := range groups { + if _, ok := allowedHttpdGroups[g]; ok { + names = append(names, string(g)) + } + } + return strings.Join(names, ":") +} + // buildHttpdConfig generates httpd.conf with dynamic TLS settings func (r *ReconcileGitopsService) buildHttpdConfig() string { minVersionTLS := string(r.CentralTLSProfile.MinTLSVersion) @@ -275,6 +304,9 @@ ServerRoot "/etc/httpd" if minVersionTLS != "VersionTLS13" && strings.Join(r.CentralTLSProfile.Ciphers, ":") != "" { httpdConfigBase += fmt.Sprintf("\n\tSSLCipherSuite %s", strings.Join(r.CentralTLSProfile.Ciphers, ":")) } + if groups := tlsGroupsForHttpd(r.CentralTLSProfile.Groups); groups != "" { + httpdConfigBase += fmt.Sprintf("\n\tSSLOpenSSLConfCmd Groups %s", groups) + } // Close VirtualHost httpdConfigBase += "\n" return httpdConfigBase diff --git a/controllers/consoleplugin_test.go b/controllers/consoleplugin_test.go index f03a553316f..611414a2252 100644 --- a/controllers/consoleplugin_test.go +++ b/controllers/consoleplugin_test.go @@ -1869,8 +1869,10 @@ func TestBuildHttpdConfig(t *testing.T) { CentralTLSProfile configv1.TLSProfileSpec wantProtocol bool wantCipherSuite bool + wantGroups bool protocol string cipherSuite string + groups string }{ { name: "no TLS version no ciphers", @@ -1880,6 +1882,7 @@ func TestBuildHttpdConfig(t *testing.T) { }, wantProtocol: false, wantCipherSuite: false, + wantGroups: false, }, { name: "TLS 1.2 with cipher suites", @@ -1889,6 +1892,7 @@ func TestBuildHttpdConfig(t *testing.T) { }, wantProtocol: true, wantCipherSuite: true, + wantGroups: false, protocol: "-all +TLSv1.2 +TLSv1.3", cipherSuite: "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", }, @@ -1900,6 +1904,7 @@ func TestBuildHttpdConfig(t *testing.T) { }, wantProtocol: true, wantCipherSuite: false, + wantGroups: false, protocol: "-all +TLSv1.3", }, { @@ -1910,6 +1915,7 @@ func TestBuildHttpdConfig(t *testing.T) { }, wantProtocol: true, wantCipherSuite: false, + wantGroups: false, protocol: "-all +TLSv1.2 +TLSv1.3", }, { @@ -1920,8 +1926,70 @@ func TestBuildHttpdConfig(t *testing.T) { }, wantProtocol: false, wantCipherSuite: true, + wantGroups: false, cipherSuite: "ECDHE-RSA-AES256-GCM-SHA384", }, + { + name: "TLS groups configure SSLOpenSSLConfCmd Groups", + CentralTLSProfile: configv1.TLSProfileSpec{ + MinTLSVersion: "VersionTLS12", + Groups: []configv1.TLSGroup{ + configv1.TLSGroupX25519MLKEM768, + configv1.TLSGroupX25519, + configv1.TLSGroupSecP256r1, + configv1.TLSGroupSecP384r1, + }, + }, + wantProtocol: true, + wantCipherSuite: false, + wantGroups: true, + protocol: "-all +TLSv1.2 +TLSv1.3", + groups: "X25519MLKEM768:X25519:secp256r1:secp384r1", + }, + { + name: "TLS 1.3 with groups", + CentralTLSProfile: configv1.TLSProfileSpec{ + MinTLSVersion: "VersionTLS13", + Groups: []configv1.TLSGroup{ + configv1.TLSGroupX25519, + configv1.TLSGroupSecP256r1, + }, + }, + wantProtocol: true, + wantCipherSuite: false, + wantGroups: true, + protocol: "-all +TLSv1.3", + groups: "X25519:secp256r1", + }, + { + name: "unknown groups are filtered out", + CentralTLSProfile: configv1.TLSProfileSpec{ + MinTLSVersion: "VersionTLS12", + Groups: []configv1.TLSGroup{ + configv1.TLSGroupX25519, + configv1.TLSGroup("UnknownGroup"), + configv1.TLSGroupSecP384r1, + }, + }, + wantProtocol: true, + wantCipherSuite: false, + wantGroups: true, + protocol: "-all +TLSv1.2 +TLSv1.3", + groups: "X25519:secp384r1", + }, + { + name: "only unknown groups omits SSLOpenSSLConfCmd", + CentralTLSProfile: configv1.TLSProfileSpec{ + MinTLSVersion: "VersionTLS12", + Groups: []configv1.TLSGroup{ + configv1.TLSGroup("UnknownGroup"), + }, + }, + wantProtocol: true, + wantCipherSuite: false, + wantGroups: false, + protocol: "-all +TLSv1.2 +TLSv1.3", + }, } for _, tt := range tests { @@ -1953,6 +2021,13 @@ func TestBuildHttpdConfig(t *testing.T) { } else { assert.Assert(t, !strings.Contains(cfg, "SSLCipherSuite")) } + + // SSLOpenSSLConfCmd Groups + if tt.wantGroups { + assert.Assert(t, cmp.Contains(cfg, "SSLOpenSSLConfCmd Groups "+tt.groups)) + } else { + assert.Assert(t, !strings.Contains(cfg, "SSLOpenSSLConfCmd Groups")) + } }) } } diff --git a/go.mod b/go.mod index 750a70848c0..d84f6b71125 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e github.com/operator-framework/api v0.17.5 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0 + github.com/samber/lo v1.53.0 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.28.0 golang.org/x/crypto v0.56.0 diff --git a/go.sum b/go.sum index 45188b83bce..08aec189afd 100644 --- a/go.sum +++ b/go.sum @@ -421,6 +421,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= +github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-password v0.4.0 h1:eSidVKQw5C7CmTDAtH3RipBTSjdU1ZRxQaynD2GWLVU= From 3cbbe79fae1e63d9d35b4821c93fcf7fa6375984 Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Thu, 8 Oct 2026 13:25:32 +0530 Subject: [PATCH 2/7] feat(tls): configure TLS curve preferences for gitops-plugin Signed-off-by: akhil nittala --- config/manager/kustomization.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 2df5e5f30be..c7ac940f7d1 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -12,5 +12,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: quay.io/nittalaakhil/openshift-gitops-operator - newTag: v5.0.0 + newName: quay.io/redhat-developer/gitops-operator From c51318b3cd2d88af65b8c581a66452bc63aa649a Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Fri, 9 Oct 2026 21:58:45 +0530 Subject: [PATCH 3/7] feat(tls): Configure TLS Curve Preferences Signed-off-by: akhil nittala --- cmd/main.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 6e10c3a15f5..a9763514c2e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -51,7 +51,6 @@ import ( operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" - "github.com/samber/lo" corev1 "k8s.io/api/core/v1" crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/labels" @@ -372,6 +371,11 @@ func main() { argocdprovisioner.Register(openshift.ReconcilerHook, openshift.BuilderHook) + curvePreferences := make([]string, len(profile.Groups)) + for i, group := range profile.Groups { + curvePreferences[i] = string(group) + } + if err = (&argocdprovisioner.ReconcileArgoCD{ Client: client, Scheme: mgr.GetScheme(), @@ -383,9 +387,7 @@ func main() { DisableClusterTLSProfile: disableClusterTLSProfile, MinVersion: profile.MinTLSVersion, Ciphers: profile.Ciphers, - CurvePreferences: lo.Map(profile.Groups, func(group configv1.TLSGroup, _ int) string { - return string(group) - }), + CurvePreferences: curvePreferences, }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Argo CD") From e288d20b4803bfb909ccd6e0d52982b7976774b1 Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Fri, 9 Oct 2026 21:59:32 +0530 Subject: [PATCH 4/7] feat(tls): Configure TLS Curve Preferences Signed-off-by: akhil nittala --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index d84f6b71125..750a70848c0 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e github.com/operator-framework/api v0.17.5 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0 - github.com/samber/lo v1.53.0 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.28.0 golang.org/x/crypto v0.56.0 diff --git a/go.sum b/go.sum index 08aec189afd..45188b83bce 100644 --- a/go.sum +++ b/go.sum @@ -421,8 +421,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= -github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-password v0.4.0 h1:eSidVKQw5C7CmTDAtH3RipBTSjdU1ZRxQaynD2GWLVU= From c423132b66156a0e9d9c500e0572bbb6c053b48d Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Wed, 14 Oct 2026 13:34:06 +0530 Subject: [PATCH 5/7] feat(tls): configure TLS Curve preferences for PQC Signed-off-by: akhil nittala --- argocd-operator/pkg/tlsprofile/profile.go | 2 +- cmd/main.go | 7 +------ controllers/consoleplugin.go | 6 ++++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/argocd-operator/pkg/tlsprofile/profile.go b/argocd-operator/pkg/tlsprofile/profile.go index 71d50b676a7..e613c4ceb17 100644 --- a/argocd-operator/pkg/tlsprofile/profile.go +++ b/argocd-operator/pkg/tlsprofile/profile.go @@ -9,5 +9,5 @@ type TLSConfigProfile struct { // Ciphers specifies the list of supported TLS cipher suites in cluster. Ciphers []string // CurvePreferences specifies the list of supported TLS curve preferences in cluster. - CurvePreferences []string + CurvePreferences []configv1.TLSGroup } diff --git a/cmd/main.go b/cmd/main.go index a9763514c2e..e6ffff779ce 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -371,11 +371,6 @@ func main() { argocdprovisioner.Register(openshift.ReconcilerHook, openshift.BuilderHook) - curvePreferences := make([]string, len(profile.Groups)) - for i, group := range profile.Groups { - curvePreferences[i] = string(group) - } - if err = (&argocdprovisioner.ReconcileArgoCD{ Client: client, Scheme: mgr.GetScheme(), @@ -387,7 +382,7 @@ func main() { DisableClusterTLSProfile: disableClusterTLSProfile, MinVersion: profile.MinTLSVersion, Ciphers: profile.Ciphers, - CurvePreferences: curvePreferences, + CurvePreferences: profile.Groups, }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Argo CD") diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go index bac312c146b..96701b67574 100644 --- a/controllers/consoleplugin.go +++ b/controllers/consoleplugin.go @@ -271,10 +271,16 @@ func tlsGroupsForHttpd(groups []configv1.TLSGroup) string { return "" } names := make([]string, 0, len(groups)) + skipped := make([]configv1.TLSGroup, 0) for _, g := range groups { if _, ok := allowedHttpdGroups[g]; ok { names = append(names, string(g)) + continue } + skipped = append(skipped, g) + } + if len(skipped) > 0 { + logs.Info("skipping unsupported TLS curve preference groups for gitops-plugin httpd", "groups", skipped) } return strings.Join(names, ":") } From 9dc447b8aa72a73cd51f59982d814b4233ed63f5 Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Wed, 14 Oct 2026 16:12:22 +0530 Subject: [PATCH 6/7] feat(tls): configure TLS Curve preferences for PQC Signed-off-by: akhil nittala --- cmd/main.go | 9 ++- controllers/consoleplugin.go | 4 +- controllers/consoleplugin_test.go | 57 ++++++++++--------- controllers/gitopsservice_controller.go | 45 +++++++++++---- controllers/gitopsservice_controller_test.go | 47 +++++++++------ ...ent_Env_Args_For_Tls_Configuration_test.go | 9 +-- 6 files changed, 106 insertions(+), 65 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index e6ffff779ce..ce179531773 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -322,8 +322,13 @@ func main() { Client: client, Scheme: mgr.GetScheme(), DisableDefaultInstall: strings.ToLower(os.Getenv(common.DisableDefaultInstallEnvVar)) == "true", - CentralTLSProfile: profile, - PluginNamespace: pluginNamespace, + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + DisableClusterTLSProfile: disableClusterTLSProfile, + MinVersion: profile.MinTLSVersion, + Ciphers: profile.Ciphers, + CurvePreferences: profile.Groups, + }, + PluginNamespace: pluginNamespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "GitopsService") os.Exit(1) diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go index 96701b67574..16accd185ec 100644 --- a/controllers/consoleplugin.go +++ b/controllers/consoleplugin.go @@ -287,7 +287,7 @@ func tlsGroupsForHttpd(groups []configv1.TLSGroup) string { // buildHttpdConfig generates httpd.conf with dynamic TLS settings func (r *ReconcileGitopsService) buildHttpdConfig() string { - minVersionTLS := string(r.CentralTLSProfile.MinTLSVersion) + minVersionTLS := string(r.CentralTLSProfile.MinVersion) httpdConfigBase := fmt.Sprintf(`LoadModule ssl_module modules/mod_ssl.so Listen %d https ServerRoot "/etc/httpd" @@ -310,7 +310,7 @@ ServerRoot "/etc/httpd" if minVersionTLS != "VersionTLS13" && strings.Join(r.CentralTLSProfile.Ciphers, ":") != "" { httpdConfigBase += fmt.Sprintf("\n\tSSLCipherSuite %s", strings.Join(r.CentralTLSProfile.Ciphers, ":")) } - if groups := tlsGroupsForHttpd(r.CentralTLSProfile.Groups); groups != "" { + if groups := tlsGroupsForHttpd(r.CentralTLSProfile.CurvePreferences); groups != "" { httpdConfigBase += fmt.Sprintf("\n\tSSLOpenSSLConfCmd Groups %s", groups) } // Close VirtualHost diff --git a/controllers/consoleplugin_test.go b/controllers/consoleplugin_test.go index 611414a2252..195c836244b 100644 --- a/controllers/consoleplugin_test.go +++ b/controllers/consoleplugin_test.go @@ -7,6 +7,7 @@ import ( "testing" argocommon "github.com/argoproj-labs/gitops-operator/argocd-operator/common" + "github.com/argoproj-labs/gitops-operator/argocd-operator/pkg/tlsprofile" configv1 "github.com/openshift/api/config/v1" consolev1 "github.com/openshift/api/console/v1" pipelinesv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" @@ -1866,7 +1867,7 @@ func TestReconcileDeployment_UpdateWhenActualChange(t *testing.T) { func TestBuildHttpdConfig(t *testing.T) { tests := []struct { name string - CentralTLSProfile configv1.TLSProfileSpec + CentralTLSProfile tlsprofile.TLSConfigProfile wantProtocol bool wantCipherSuite bool wantGroups bool @@ -1876,9 +1877,9 @@ func TestBuildHttpdConfig(t *testing.T) { }{ { name: "no TLS version no ciphers", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "", - Ciphers: nil, + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "", + Ciphers: nil, }, wantProtocol: false, wantCipherSuite: false, @@ -1886,9 +1887,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "TLS 1.2 with cipher suites", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS12", - Ciphers: []string{"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-RSA-AES128-GCM-SHA256"}, + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "VersionTLS12", + Ciphers: []string{"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-RSA-AES128-GCM-SHA256"}, }, wantProtocol: true, wantCipherSuite: true, @@ -1898,9 +1899,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "TLS 1.3 ignores cipher suites", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS13", - Ciphers: []string{"TLS_AES_128_GCM_SHA256"}, + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "VersionTLS13", + Ciphers: []string{"TLS_AES_128_GCM_SHA256"}, }, wantProtocol: true, wantCipherSuite: false, @@ -1909,9 +1910,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "TLS 1.2 without cipher suites", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS12", - Ciphers: nil, + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "VersionTLS12", + Ciphers: nil, }, wantProtocol: true, wantCipherSuite: false, @@ -1920,9 +1921,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "invalid TLS version with cipher suites", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "invalid", - Ciphers: []string{"ECDHE-RSA-AES256-GCM-SHA384"}, + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "invalid", + Ciphers: []string{"ECDHE-RSA-AES256-GCM-SHA384"}, }, wantProtocol: false, wantCipherSuite: true, @@ -1931,9 +1932,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "TLS groups configure SSLOpenSSLConfCmd Groups", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS12", - Groups: []configv1.TLSGroup{ + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "VersionTLS12", + CurvePreferences: []configv1.TLSGroup{ configv1.TLSGroupX25519MLKEM768, configv1.TLSGroupX25519, configv1.TLSGroupSecP256r1, @@ -1948,9 +1949,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "TLS 1.3 with groups", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS13", - Groups: []configv1.TLSGroup{ + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "VersionTLS13", + CurvePreferences: []configv1.TLSGroup{ configv1.TLSGroupX25519, configv1.TLSGroupSecP256r1, }, @@ -1963,9 +1964,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "unknown groups are filtered out", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS12", - Groups: []configv1.TLSGroup{ + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "VersionTLS12", + CurvePreferences: []configv1.TLSGroup{ configv1.TLSGroupX25519, configv1.TLSGroup("UnknownGroup"), configv1.TLSGroupSecP384r1, @@ -1979,9 +1980,9 @@ func TestBuildHttpdConfig(t *testing.T) { }, { name: "only unknown groups omits SSLOpenSSLConfCmd", - CentralTLSProfile: configv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS12", - Groups: []configv1.TLSGroup{ + CentralTLSProfile: tlsprofile.TLSConfigProfile{ + MinVersion: "VersionTLS12", + CurvePreferences: []configv1.TLSGroup{ configv1.TLSGroup("UnknownGroup"), }, }, diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index d37b887aec1..58ab3acbf19 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -28,8 +28,10 @@ import ( argocommon "github.com/argoproj-labs/gitops-operator/argocd-operator/common" argocdcontroller "github.com/argoproj-labs/gitops-operator/argocd-operator/controllers/argocd" argocdutil "github.com/argoproj-labs/gitops-operator/argocd-operator/controllers/argoutil" + "github.com/argoproj-labs/gitops-operator/argocd-operator/pkg/tlsprofile" "github.com/go-logr/logr" version "github.com/hashicorp/go-version" + configv1 "github.com/openshift/api/config/v1" routev1 "github.com/openshift/api/route/v1" pipelinesv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" "github.com/redhat-developer/gitops-operator/common" @@ -54,8 +56,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - configv1 "github.com/openshift/api/config/v1" ) var logs = logf.Log.WithName("controller_gitopsservice") @@ -148,8 +148,8 @@ type ReconcileGitopsService struct { // disableDefaultInstall, if true, will ensure that the default ArgoCD instance is not instantiated in the openshift-gitops namespace. DisableDefaultInstall bool - //CentralTLSProfile contains MinVersion and CipherSuites - CentralTLSProfile configv1.TLSProfileSpec + // CentralTLSProfile contains cluster TLS settings (min version, ciphers, curve preferences). + CentralTLSProfile tlsprofile.TLSConfigProfile // PluginNamespace is the namespace where console plugin resources (Deployment, Service, ConfigMap) are deployed. // This is typically the operator's own namespace (e.g. "openshift-gitops-operator"). @@ -834,7 +834,7 @@ func objectMeta(resourceName string, namespace string, opts ...func(*metav1.Obje return objectMeta } -func newBackendDeployment(ns types.NamespacedName, crImagePullPolicy corev1.PullPolicy, CentralTLSProfile configv1.TLSProfileSpec) *appsv1.Deployment { +func newBackendDeployment(ns types.NamespacedName, crImagePullPolicy corev1.PullPolicy, CentralTLSProfile tlsprofile.TLSConfigProfile) *appsv1.Deployment { image := os.Getenv(backendImageEnvName) if image == "" { image = backendImage @@ -845,18 +845,39 @@ func newBackendDeployment(ns types.NamespacedName, crImagePullPolicy corev1.Pull Value: insecureEnvVarValue, }, } - if argocdutil.TLSProtocolVersionString(CentralTLSProfile.MinTLSVersion) != "" { + if argocdutil.TLSProtocolVersionString(CentralTLSProfile.MinVersion) != "" { env = append(env, corev1.EnvVar{ Name: "TLS_MIN_VERSION", - Value: argocdutil.TLSProtocolVersionString(CentralTLSProfile.MinTLSVersion), + Value: argocdutil.TLSProtocolVersionString(CentralTLSProfile.MinVersion), }) } - if len(CentralTLSProfile.Ciphers) > 0 { - env = append(env, corev1.EnvVar{ - Name: "TLS_CIPHER_SUITES", - Value: strings.Join(CentralTLSProfile.Ciphers, ":"), - }) + // gitops-backend is Go-based and accepts IANA cipher names only (e.g. + // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), not OpenSSL names from the + // OpenShift TLSSecurityProfile (e.g. ECDHE-ECDSA-AES128-GCM-SHA256). + if ciphers := argocdutil.MapCipherSuites(CentralTLSProfile.Ciphers); len(ciphers) > 0 { + // Go does not allow configuring TLS 1.3 cipher suites; gitops-backend + // also rejects TLS 1.3 suites when the configured minimum is below 1.3. + if CentralTLSProfile.MinVersion != configv1.VersionTLS13 { + tls13Ciphers := map[string]bool{ + "TLS_AES_128_GCM_SHA256": true, + "TLS_AES_256_GCM_SHA384": true, + "TLS_CHACHA20_POLY1305_SHA256": true, + } + filtered := make([]string, 0, len(ciphers)) + for _, cipher := range ciphers { + if !tls13Ciphers[cipher] { + filtered = append(filtered, cipher) + } + } + ciphers = filtered + } + if len(ciphers) > 0 { + env = append(env, corev1.EnvVar{ + Name: "TLS_CIPHER_SUITES", + Value: strings.Join(ciphers, ":"), + }) + } } podSpec := corev1.PodSpec{ Containers: []corev1.Container{ diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index 160b96c82ae..4099132a3a2 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -25,6 +25,7 @@ import ( argoapp "github.com/argoproj-labs/gitops-operator/argocd-operator/api/v1beta1" argocommon "github.com/argoproj-labs/gitops-operator/argocd-operator/common" "github.com/argoproj-labs/gitops-operator/argocd-operator/controllers/argocd" + "github.com/argoproj-labs/gitops-operator/argocd-operator/pkg/tlsprofile" configv1 "github.com/openshift/api/config/v1" consolev1 "github.com/openshift/api/console/v1" routev1 "github.com/openshift/api/route/v1" @@ -62,7 +63,7 @@ func TestImageFromEnvVariable(t *testing.T) { image := "quay.io/org/test" t.Setenv(backendImageEnvName, image) - deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), configv1.TLSProfileSpec{}) + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), tlsprofile.TLSConfigProfile{}) got := deployment.Spec.Template.Spec.Containers[0].Image if got != image { @@ -70,7 +71,7 @@ func TestImageFromEnvVariable(t *testing.T) { } }) t.Run("env variable for image not found", func(t *testing.T) { - deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), configv1.TLSProfileSpec{}) + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), tlsprofile.TLSConfigProfile{}) got := deployment.Spec.Template.Spec.Containers[0].Image if got != backendImage { @@ -79,7 +80,7 @@ func TestImageFromEnvVariable(t *testing.T) { }) t.Run("TLS Min Version 1.3 and empty ciphers", func(t *testing.T) { - deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), configv1.TLSProfileSpec{MinTLSVersion: configv1.VersionTLS13}) + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), tlsprofile.TLSConfigProfile{MinVersion: configv1.VersionTLS13}) var gotTLSMinVersion string for _, env := range deployment.Spec.Template.Spec.Containers[0].Env { if env.Name == "TLS_MIN_VERSION" { @@ -93,7 +94,7 @@ func TestImageFromEnvVariable(t *testing.T) { }) t.Run("TLS Min Version 1.2 and empty ciphers", func(t *testing.T) { - deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), configv1.TLSProfileSpec{MinTLSVersion: configv1.VersionTLS12}) + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), tlsprofile.TLSConfigProfile{MinVersion: configv1.VersionTLS12}) var gotTLSMinVersion string for _, env := range deployment.Spec.Template.Spec.Containers[0].Env { if env.Name == "TLS_MIN_VERSION" { @@ -106,43 +107,55 @@ func TestImageFromEnvVariable(t *testing.T) { } }) - t.Run("TLS Min Version 1.3 and single ciphers", func(t *testing.T) { - deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), configv1.TLSProfileSpec{MinTLSVersion: configv1.VersionTLS13, Ciphers: []string{"dummy1"}}) + t.Run("TLS Min Version 1.3 maps OpenSSL ciphers to IANA names", func(t *testing.T) { + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), tlsprofile.TLSConfigProfile{ + MinVersion: configv1.VersionTLS13, + Ciphers: []string{"ECDHE-ECDSA-AES128-GCM-SHA256"}, + }) var gotTLSMinVersion string var gotTLSCiphers string for _, env := range deployment.Spec.Template.Spec.Containers[0].Env { if env.Name == "TLS_MIN_VERSION" { gotTLSMinVersion = env.Value - break } if env.Name == "TLS_CIPHER_SUITES" { gotTLSCiphers = env.Value - break } } - if gotTLSMinVersion != "1.3" && gotTLSCiphers != "dummy1" { + if gotTLSMinVersion != "1.3" { t.Errorf("TLS Min Version mismatch: got %s, want %s", gotTLSMinVersion, "1.3") - t.Errorf("TLS Cipher mismatch: got %s, want %s", gotTLSCiphers, "dummy1") + } + want := "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + if gotTLSCiphers != want { + t.Errorf("TLS Cipher mismatch: got %s, want %s", gotTLSCiphers, want) } }) - t.Run("TLS Min Version 1.3 and double ciphers", func(t *testing.T) { - deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), configv1.TLSProfileSpec{MinTLSVersion: configv1.VersionTLS13, Ciphers: []string{"dummy1", "dummy2"}}) + t.Run("TLS Min Version 1.2 maps and filters TLS 1.3 cipher suites", func(t *testing.T) { + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent), tlsprofile.TLSConfigProfile{ + MinVersion: configv1.VersionTLS12, + Ciphers: []string{ + "TLS_AES_128_GCM_SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + }, + }) var gotTLSMinVersion string var gotTLSCiphers string for _, env := range deployment.Spec.Template.Spec.Containers[0].Env { if env.Name == "TLS_MIN_VERSION" { gotTLSMinVersion = env.Value - break } if env.Name == "TLS_CIPHER_SUITES" { gotTLSCiphers = env.Value - break } } - if gotTLSMinVersion != "1.3" && gotTLSCiphers != "dummy1:dummy2" { - t.Errorf("TLS Min Version mismatch: got %s, want %s", gotTLSMinVersion, "1.3") - t.Errorf("TLS Cipher mismatch: got %s, want %s", gotTLSCiphers, "dummy1:dummy2") + if gotTLSMinVersion != "1.2" { + t.Errorf("TLS Min Version mismatch: got %s, want %s", gotTLSMinVersion, "1.2") + } + want := "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + if gotTLSCiphers != want { + t.Errorf("TLS Cipher mismatch: got %s, want %s", gotTLSCiphers, want) } }) } diff --git a/test/openshift/e2e/ginkgo/sequential/1-143_validate_deployment_Env_Args_For_Tls_Configuration_test.go b/test/openshift/e2e/ginkgo/sequential/1-143_validate_deployment_Env_Args_For_Tls_Configuration_test.go index e7e0db055c5..81713ca4306 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-143_validate_deployment_Env_Args_For_Tls_Configuration_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-143_validate_deployment_Env_Args_For_Tls_Configuration_test.go @@ -264,10 +264,11 @@ var _ = Describe("Validate Deployment Env Args For TLS Configuration", Label("op })) Expect(env).To(ContainElement(corev1.EnvVar{ Name: "TLS_CIPHER_SUITES", - Value: "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:" + - "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" + - "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" + - "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305", + // OpenSSL names from the cluster TLS profile are mapped to IANA/Go + // names; TLS 1.3 suites are omitted when min version is 1.2. + Value: "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", })) return nil From 796f53deae0ff206e78a8240c48c2e94ae2ba61c Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Wed, 14 Oct 2026 16:46:19 +0530 Subject: [PATCH 7/7] feat(tls): configure TLS Curve preferences for PQC Signed-off-by: akhil nittala --- cmd/main.go | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index ce179531773..145a3227a76 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -317,18 +317,20 @@ func main() { os.Exit(1) } + centralTLSConfigProfile := tlsprofile.TLSConfigProfile{ + DisableClusterTLSProfile: disableClusterTLSProfile, + MinVersion: profile.MinTLSVersion, + Ciphers: profile.Ciphers, + CurvePreferences: profile.Groups, + } + if util.IsOpenShiftCluster() { if err = (&controllers.ReconcileGitopsService{ Client: client, Scheme: mgr.GetScheme(), DisableDefaultInstall: strings.ToLower(os.Getenv(common.DisableDefaultInstallEnvVar)) == "true", - CentralTLSProfile: tlsprofile.TLSConfigProfile{ - DisableClusterTLSProfile: disableClusterTLSProfile, - MinVersion: profile.MinTLSVersion, - Ciphers: profile.Ciphers, - CurvePreferences: profile.Groups, - }, - PluginNamespace: pluginNamespace, + CentralTLSProfile: centralTLSConfigProfile, + PluginNamespace: pluginNamespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "GitopsService") os.Exit(1) @@ -377,18 +379,13 @@ func main() { argocdprovisioner.Register(openshift.ReconcilerHook, openshift.BuilderHook) if err = (&argocdprovisioner.ReconcileArgoCD{ - Client: client, - Scheme: mgr.GetScheme(), - LabelSelector: labelSelectorFlag, - K8sClient: k8sClient, - LocalUsers: argocdprovisioner.NewLocalUsersInfo(), - FipsConfigChecker: argoutil.NewLinuxFipsConfigChecker(), - CentralTLSConfigProfile: tlsprofile.TLSConfigProfile{ - DisableClusterTLSProfile: disableClusterTLSProfile, - MinVersion: profile.MinTLSVersion, - Ciphers: profile.Ciphers, - CurvePreferences: profile.Groups, - }, + Client: client, + Scheme: mgr.GetScheme(), + LabelSelector: labelSelectorFlag, + K8sClient: k8sClient, + LocalUsers: argocdprovisioner.NewLocalUsersInfo(), + FipsConfigChecker: argoutil.NewLinuxFipsConfigChecker(), + CentralTLSConfigProfile: centralTLSConfigProfile, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Argo CD") os.Exit(1)