From 45bf8d1ccd4dc20e3e2e6947dc8b736a789fb69b Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Fri, 23 Oct 2026 15:44:39 +0530 Subject: [PATCH 1/2] feat(tls): Configure Image Updater Curve Preferences Signed-off-by: akhil nittala --- .../controllers/argocd/deployment.go | 27 ++++++++-- .../controllers/argocd/deployment_test.go | 50 +++++++++++++++++- .../controllers/argocd/image_updater.go | 5 +- .../controllers/argocd/image_updater_test.go | 52 +++++++++++++++++-- .../controllers/argocd/repo_server.go | 9 +++- 5 files changed, 130 insertions(+), 13 deletions(-) diff --git a/argocd-operator/controllers/argocd/deployment.go b/argocd-operator/controllers/argocd/deployment.go index 5d9a63c87af..2589a5c0787 100644 --- a/argocd-operator/controllers/argocd/deployment.go +++ b/argocd-operator/controllers/argocd/deployment.go @@ -984,7 +984,14 @@ func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoproj.ArgoCD, useTLSF if cr.Spec.Server.VolumeMounts != nil { serverVolumeMounts = append(serverVolumeMounts, cr.Spec.Server.VolumeMounts...) } - arguments := BuildTLSArgsFromClusterTLSProfile(r.CentralTLSConfigProfile) + // argocd-server does not support --tlscurvepreferences yet; omit curves until a + // supporting image is the default. Keep min version and ciphers. + tlsProfileForServer := r.CentralTLSConfigProfile + tlsProfileForServer.CurvePreferences = nil + arguments, err := BuildTLSArgsFromClusterTLSProfile(tlsProfileForServer, "argocd-server") + if err != nil { + return err + } deploy.Spec.Template.Spec.Containers = []corev1.Container{{ Args: arguments, Command: getArgoServerCommand(cr, useTLSForRedis), @@ -1329,11 +1336,14 @@ func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoproj.ArgoCD, useTLSF return r.Create(context.TODO(), deploy) } -// BuildTLSArgsFromClusterTLSProfile builds the command line arguments for the ArgoCD components based on the cluster's TLS profile configuration. -func BuildTLSArgsFromClusterTLSProfile(centralTLSConfig tlsProfile.TLSConfigProfile) []string { +// BuildTLSArgsFromClusterTLSProfile builds the command line arguments for Argo CD +// components based on the cluster's TLS profile configuration. +// component identifies the caller in curve-preference error/log messages +// (e.g. "argocd-server", "argocd-repo-server", "argocd-image-updater"). +func BuildTLSArgsFromClusterTLSProfile(centralTLSConfig tlsProfile.TLSConfigProfile, component string) ([]string, error) { var args []string if centralTLSConfig.DisableClusterTLSProfile { - return nil + return nil, nil } if v := argoutil.TLSProtocolVersionString(centralTLSConfig.MinVersion); v != "" { args = append(args, "--tlsminversion", v) @@ -1359,7 +1369,14 @@ func BuildTLSArgsFromClusterTLSProfile(centralTLSConfig tlsProfile.TLSConfigProf args = append(args, "--tlsciphers", strings.Join(ciphers, ":")) } } - return args + curves, err := argoutil.MapOpenShiftTLSGroupsToGoCurves(centralTLSConfig.CurvePreferences, component) + if err != nil { + return nil, err + } + if curves != "" { + args = append(args, "--tlscurvepreferences", curves) + } + return args, nil } // triggerDeploymentRollout will update the label with the given key to trigger a new rollout of the Deployment. diff --git a/argocd-operator/controllers/argocd/deployment_test.go b/argocd-operator/controllers/argocd/deployment_test.go index 74589cc9b5c..27aa3ec0fb4 100644 --- a/argocd-operator/controllers/argocd/deployment_test.go +++ b/argocd-operator/controllers/argocd/deployment_test.go @@ -3497,11 +3497,46 @@ func TestBuildTLSArgs(t *testing.T) { }, wantErr: false, }, + { + name: "central TLS config with curve preferences", + centralTLS: tlsProfile.TLSConfigProfile{ + MinVersion: configv1.VersionTLS12, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupX25519MLKEM768, + configv1.TLSGroupX25519, + configv1.TLSGroupSecP256r1, + }, + }, + expected: []string{ + "--tlsminversion", + "1.2", + "--tlscurvepreferences", + "X25519MLKEM768:X25519:CurveP256", + }, + wantErr: false, + }, + { + name: "errors when all curve preferences are unsupported", + centralTLS: tlsProfile.TLSConfigProfile{ + MinVersion: configv1.VersionTLS12, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + }, + }, + expected: nil, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := BuildTLSArgsFromClusterTLSProfile(tt.centralTLS) + got, err := BuildTLSArgsFromClusterTLSProfile(tt.centralTLS, "test") + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, got) + return + } + assert.NoError(t, err) if !reflect.DeepEqual(got, tt.expected) { t.Fatalf("expected %#v got %#v", tt.expected, got) } @@ -3525,15 +3560,26 @@ func TestArgoCDServerAndRepoServerDeploymentArgs(t *testing.T) { "ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-RSA-AES256-GCM-SHA384", }, + // Curve preferences are set on the central profile but omitted for + // server/repo-server until those components support --tlscurvepreferences. + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupX25519, + configv1.TLSGroupSecP256r1, + }, } deployment := &appsv1.Deployment{} assert.NoError(t, r.reconcileServerDeployment(a, false)) assert.NoError(t, r.Get(context.TODO(), types.NamespacedName{Name: "argocd-server", Namespace: a.Namespace}, deployment)) - args := []string{"--tlsminversion", "1.2", "--tlsciphers", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"} + args := []string{ + "--tlsminversion", "1.2", + "--tlsciphers", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + } assert.Equal(t, args, deployment.Spec.Template.Spec.Containers[0].Args) + assert.NotContains(t, deployment.Spec.Template.Spec.Containers[0].Args, "--tlscurvepreferences") assert.NoError(t, r.reconcileRepoDeployment(a, false)) assert.NoError(t, r.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server", Namespace: a.Namespace}, deployment)) assert.Equal(t, args, deployment.Spec.Template.Spec.Containers[0].Args) + assert.NotContains(t, deployment.Spec.Template.Spec.Containers[0].Args, "--tlscurvepreferences") } func TestReconcileServer_PromoterUIExtension(t *testing.T) { diff --git a/argocd-operator/controllers/argocd/image_updater.go b/argocd-operator/controllers/argocd/image_updater.go index da28d334a74..ce81be070bc 100644 --- a/argocd-operator/controllers/argocd/image_updater.go +++ b/argocd-operator/controllers/argocd/image_updater.go @@ -715,7 +715,10 @@ func (r *ReconcileArgoCD) reconcileImageUpdaterDeployment(cr *argoproj.ArgoCD, s } args := []string{"run"} - imageUpdaterTLSProfileArguments := BuildTLSArgsFromClusterTLSProfile(r.CentralTLSConfigProfile) + imageUpdaterTLSProfileArguments, err := BuildTLSArgsFromClusterTLSProfile(r.CentralTLSConfigProfile, "argocd-image-updater") + if err != nil { + return err + } args = append(args, imageUpdaterTLSProfileArguments...) podSpec.Containers = []corev1.Container{{ diff --git a/argocd-operator/controllers/argocd/image_updater_test.go b/argocd-operator/controllers/argocd/image_updater_test.go index 52df994e69d..929f90cda2d 100644 --- a/argocd-operator/controllers/argocd/image_updater_test.go +++ b/argocd-operator/controllers/argocd/image_updater_test.go @@ -1405,9 +1405,11 @@ func TestExpandImageUpdaterWatchNamespaces(t *testing.T) { func TestReconcileImageUpdaterDeployment_TLSArgs(t *testing.T) { tests := []struct { - name string - centralTLS tlsProfile.TLSConfigProfile - expectedArgs []string + name string + centralTLS tlsProfile.TLSConfigProfile + expectedArgs []string + unexpectedArgs []string + wantErr bool }{ { name: "central tls profile", @@ -1426,12 +1428,47 @@ func TestReconcileImageUpdaterDeployment_TLSArgs(t *testing.T) { "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", }, }, + { + name: "central tls profile with curve preferences", + centralTLS: tlsProfile.TLSConfigProfile{ + DisableClusterTLSProfile: false, + MinVersion: configv1.VersionTLS12, + Ciphers: []string{ + "ECDHE-RSA-AES128-GCM-SHA256", + }, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupX25519MLKEM768, + configv1.TLSGroupX25519, + configv1.TLSGroupSecP256r1, + }, + }, + expectedArgs: []string{ + "--tlsminversion", + "1.2", + "--tlsciphers", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "--tlscurvepreferences", + "X25519MLKEM768:X25519:CurveP256", + }, + }, { name: "Disable cluster tls profile", centralTLS: tlsProfile.TLSConfigProfile{ DisableClusterTLSProfile: true, }, - expectedArgs: []string{}, + expectedArgs: []string{}, + unexpectedArgs: []string{"--tlsminversion", "--tlsciphers", "--tlscurvepreferences"}, + }, + { + name: "errors when all curve preferences are unsupported", + centralTLS: tlsProfile.TLSConfigProfile{ + DisableClusterTLSProfile: false, + MinVersion: configv1.VersionTLS12, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + }, + }, + wantErr: true, }, } for _, tt := range tests { @@ -1451,6 +1488,10 @@ func TestReconcileImageUpdaterDeployment_TLSArgs(t *testing.T) { CentralTLSConfigProfile: tt.centralTLS, } err := r.reconcileImageUpdaterDeployment(cr, sa, "", nil) + if tt.wantErr { + require.Error(t, err) + return + } require.NoError(t, err) deployment := &appsv1.Deployment{} err = client.Get(context.TODO(), types.NamespacedName{Name: nameWithSuffix(common.ArgoCDImageUpdaterControllerComponent, cr), Namespace: cr.Namespace}, deployment) @@ -1459,6 +1500,9 @@ func TestReconcileImageUpdaterDeployment_TLSArgs(t *testing.T) { for _, expected := range tt.expectedArgs { assert.Contains(t, args, expected) } + for _, unexpected := range tt.unexpectedArgs { + assert.NotContains(t, args, unexpected) + } }) } } diff --git a/argocd-operator/controllers/argocd/repo_server.go b/argocd-operator/controllers/argocd/repo_server.go index 7d44b698cd0..9292f5629a3 100644 --- a/argocd-operator/controllers/argocd/repo_server.go +++ b/argocd-operator/controllers/argocd/repo_server.go @@ -211,7 +211,14 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argocdoperatorv1beta1.Argo repoServerVolumeMounts = append(repoServerVolumeMounts, cr.Spec.Repo.VolumeMounts...) } - arguments := BuildTLSArgsFromClusterTLSProfile(r.CentralTLSConfigProfile) + // argocd-repo-server does not support --tlscurvepreferences yet; omit curves until a + // supporting image is the default. Keep min version and ciphers. + tlsProfileForRepoServer := r.CentralTLSConfigProfile + tlsProfileForRepoServer.CurvePreferences = nil + arguments, err := BuildTLSArgsFromClusterTLSProfile(tlsProfileForRepoServer, "argocd-repo-server") + if err != nil { + return err + } deploy.Spec.Template.Spec.Containers = []corev1.Container{{ Args: arguments, Command: getArgoRepoCommand(cr, useTLSForRedis), From 03c4a464b1eccb288251807025f2c3b183f91da1 Mon Sep 17 00:00:00 2001 From: akhil nittala Date: Mon, 26 Oct 2026 17:07:06 +0530 Subject: [PATCH 2/2] feat(tls): Configure Image Updater Curve Preferences Signed-off-by: akhil nittala --- .../controllers/argocd/deployment.go | 9 +++-- .../controllers/argocd/deployment_test.go | 9 +++-- .../controllers/argocd/image_updater_test.go | 38 ++++++++++++++++++- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/argocd-operator/controllers/argocd/deployment.go b/argocd-operator/controllers/argocd/deployment.go index 2589a5c0787..1185c2cd1e4 100644 --- a/argocd-operator/controllers/argocd/deployment.go +++ b/argocd-operator/controllers/argocd/deployment.go @@ -1371,9 +1371,12 @@ func BuildTLSArgsFromClusterTLSProfile(centralTLSConfig tlsProfile.TLSConfigProf } curves, err := argoutil.MapOpenShiftTLSGroupsToGoCurves(centralTLSConfig.CurvePreferences, component) if err != nil { - return nil, err - } - if curves != "" { + // Do not fail reconciliation for incompatible curve preferences. Keep min + // version/ciphers and fall back to the component's default curve selection + // so disable/delete paths (and other reconcile work) can still proceed. + log.Info("skipping TLS curve preferences; falling back to component defaults", + "component", component, "error", err.Error()) + } else if curves != "" { args = append(args, "--tlscurvepreferences", curves) } return args, nil diff --git a/argocd-operator/controllers/argocd/deployment_test.go b/argocd-operator/controllers/argocd/deployment_test.go index 27aa3ec0fb4..91cf025385a 100644 --- a/argocd-operator/controllers/argocd/deployment_test.go +++ b/argocd-operator/controllers/argocd/deployment_test.go @@ -3516,15 +3516,18 @@ func TestBuildTLSArgs(t *testing.T) { wantErr: false, }, { - name: "errors when all curve preferences are unsupported", + name: "omits curve preferences when all groups are unsupported", centralTLS: tlsProfile.TLSConfigProfile{ MinVersion: configv1.VersionTLS12, CurvePreferences: []configv1.TLSGroup{ configv1.TLSGroupSecP256r1MLKEM768, }, }, - expected: nil, - wantErr: true, + expected: []string{ + "--tlsminversion", + "1.2", + }, + wantErr: false, }, } diff --git a/argocd-operator/controllers/argocd/image_updater_test.go b/argocd-operator/controllers/argocd/image_updater_test.go index 10fbf0018a8..6598cc90019 100644 --- a/argocd-operator/controllers/argocd/image_updater_test.go +++ b/argocd-operator/controllers/argocd/image_updater_test.go @@ -1461,7 +1461,7 @@ func TestReconcileImageUpdaterDeployment_TLSArgs(t *testing.T) { unexpectedArgs: []string{"--tlsminversion", "--tlsciphers", "--tlscurvepreferences"}, }, { - name: "errors when all curve preferences are unsupported", + name: "omits curve preferences when all groups are unsupported", centralTLS: tlsProfile.TLSConfigProfile{ DisableClusterTLSProfile: false, MinVersion: configv1.VersionTLS12, @@ -1469,7 +1469,11 @@ func TestReconcileImageUpdaterDeployment_TLSArgs(t *testing.T) { configv1.TLSGroupSecP256r1MLKEM768, }, }, - wantErr: true, + expectedArgs: []string{ + "--tlsminversion", + "1.2", + }, + unexpectedArgs: []string{"--tlscurvepreferences"}, }, } for _, tt := range tests { @@ -1508,6 +1512,36 @@ func TestReconcileImageUpdaterDeployment_TLSArgs(t *testing.T) { } } +func TestReconcileImageUpdaterDeployment_DisabledWithUnsupportedCurves(t *testing.T) { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = v1.AddToScheme(scheme) + _ = argoproj.AddToScheme(scheme) + + cr := makeTestArgoCD() + cr.Spec.ImageUpdater.Enabled = false + sa := &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "test-sa", Namespace: cr.Namespace}} + existing := newDeploymentWithSuffix(common.ArgoCDImageUpdaterControllerComponent, "controller", cr) + client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr, sa, existing).Build() + r := &ReconcileArgoCD{ + Client: client, + Scheme: scheme, + CentralTLSConfigProfile: tlsProfile.TLSConfigProfile{ + DisableClusterTLSProfile: false, + MinVersion: configv1.VersionTLS12, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + }, + }, + } + + require.NoError(t, r.reconcileImageUpdaterDeployment(cr, sa, "", nil)) + + deployment := &appsv1.Deployment{} + err := client.Get(context.TODO(), types.NamespacedName{Name: existing.Name, Namespace: cr.Namespace}, deployment) + assert.True(t, errors.IsNotFound(err), "disabled Image Updater deployment should be deleted even when curve preferences are unsupported") +} + func TestSelectImageUpdaterImage(t *testing.T) { tests := []struct { name string