diff --git a/argocd-operator/controllers/argocd/deployment.go b/argocd-operator/controllers/argocd/deployment.go index 5d9a63c87af..1185c2cd1e4 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,17 @@ func BuildTLSArgsFromClusterTLSProfile(centralTLSConfig tlsProfile.TLSConfigProf args = append(args, "--tlsciphers", strings.Join(ciphers, ":")) } } - return args + curves, err := argoutil.MapOpenShiftTLSGroupsToGoCurves(centralTLSConfig.CurvePreferences, component) + if err != nil { + // 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 } // 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..91cf025385a 100644 --- a/argocd-operator/controllers/argocd/deployment_test.go +++ b/argocd-operator/controllers/argocd/deployment_test.go @@ -3497,11 +3497,49 @@ 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: "omits curve preferences when all groups are unsupported", + centralTLS: tlsProfile.TLSConfigProfile{ + MinVersion: configv1.VersionTLS12, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + }, + }, + expected: []string{ + "--tlsminversion", + "1.2", + }, + wantErr: false, + }, } 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 +3563,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 bb2ad0c81ac..be27609ec94 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...) // Command is intentionally not set so that the image's own ENTRYPOINT applies. diff --git a/argocd-operator/controllers/argocd/image_updater_test.go b/argocd-operator/controllers/argocd/image_updater_test.go index 64efee12ab0..6598cc90019 100644 --- a/argocd-operator/controllers/argocd/image_updater_test.go +++ b/argocd-operator/controllers/argocd/image_updater_test.go @@ -1406,9 +1406,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", @@ -1427,12 +1429,51 @@ 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: "omits curve preferences when all groups are unsupported", + centralTLS: tlsProfile.TLSConfigProfile{ + DisableClusterTLSProfile: false, + MinVersion: configv1.VersionTLS12, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + }, + }, + expectedArgs: []string{ + "--tlsminversion", + "1.2", + }, + unexpectedArgs: []string{"--tlscurvepreferences"}, }, } for _, tt := range tests { @@ -1452,6 +1493,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) @@ -1460,10 +1505,43 @@ 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) + } }) } } +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 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),