diff --git a/argocd-operator/common/defaults.go b/argocd-operator/common/defaults.go index 2143eaca5bb..b402778a4b9 100644 --- a/argocd-operator/common/defaults.go +++ b/argocd-operator/common/defaults.go @@ -312,10 +312,10 @@ vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOf ArgoCDCmdParamsConfigMapName = "argocd-cmd-params-cm" // ArgoCDAgentPrincipalDefaultImageName is the default image name for the ArgoCD agent's principal component. - ArgoCDAgentPrincipalDefaultImageName = "quay.io/argoprojlabs/argocd-agent:v0.10.0" + ArgoCDAgentPrincipalDefaultImageName = "ghcr.io/argoproj-labs/argocd-agent/argocd-agent:3588f4ca" // ArgoCDAgentAgentDefaultImageName is the default image name for the ArgoCD agent's agent component. - ArgoCDAgentAgentDefaultImageName = "quay.io/argoprojlabs/argocd-agent:v0.10.0" + ArgoCDAgentAgentDefaultImageName = "ghcr.io/argoproj-labs/argocd-agent/argocd-agent:3588f4ca" // ArgoCDImageUpdaterControllerComponent is the name of the Image Updater controller control plane component ArgoCDImageUpdaterControllerComponent = "argocd-image-updater-controller" diff --git a/argocd-operator/controllers/argocdagent/deployment.go b/argocd-operator/controllers/argocdagent/deployment.go index 65ed63b7bfe..f1be968a2c5 100644 --- a/argocd-operator/controllers/argocdagent/deployment.go +++ b/argocd-operator/controllers/argocdagent/deployment.go @@ -61,7 +61,10 @@ func ReconcilePrincipalDeployment(client client.Client, compName, saName string, return nil } - deployment, changed := updateDeploymentIfChanged(compName, saName, cr, deployment, centralTLSProfile) + deployment, changed, err := updateDeploymentIfChanged(compName, saName, cr, deployment, centralTLSProfile) + if err != nil { + return err + } if changed { argoutil.LogResourceUpdate(log, deployment, "principal deployment is being updated") if err := client.Update(context.TODO(), deployment); err != nil { @@ -81,7 +84,11 @@ func ReconcilePrincipalDeployment(client client.Client, compName, saName string, } argoutil.LogResourceCreation(log, deployment) - deployment.Spec = buildPrincipalSpec(compName, saName, cr, centralTLSProfile) + spec, err := buildPrincipalSpec(compName, saName, cr, centralTLSProfile) + if err != nil { + return err + } + deployment.Spec = spec if err := client.Create(context.TODO(), deployment); err != nil { return fmt.Errorf("failed to create principal deployment %s in namespace %s: %v", deployment.Name, cr.Namespace, err) } @@ -98,9 +105,12 @@ func buildDeployment(compName string, cr *argoproj.ArgoCD) *appsv1.Deployment { } } -func buildPrincipalSpec(compName, saName string, cr *argoproj.ArgoCD, centralTLSProfile tlsProfile.TLSConfigProfile) appsv1.DeploymentSpec { +func buildPrincipalSpec(compName, saName string, cr *argoproj.ArgoCD, centralTLSProfile tlsProfile.TLSConfigProfile) (appsv1.DeploymentSpec, error) { redisAuthVolume, redisAuthMount := argoutil.MountRedisAuthToArgo(cr) - envParams := buildPrincipalContainerEnv(cr, centralTLSProfile) + envParams, err := buildPrincipalContainerEnv(cr, centralTLSProfile) + if err != nil { + return appsv1.DeploymentSpec{}, err + } return appsv1.DeploymentSpec{ Selector: buildSelector(compName, cr), Template: corev1.PodTemplateSpec{ @@ -126,7 +136,7 @@ func buildPrincipalSpec(compName, saName string, cr *argoproj.ArgoCD, centralTLS PriorityClassName: cr.Spec.PriorityClassName, }, }, - } + }, nil } func buildSelector(compName string, cr *argoproj.ArgoCD) *metav1.LabelSelector { @@ -251,9 +261,9 @@ func buildVolumes() []corev1.Volume { } // updateDeploymentIfChanged compares the current deployment with the desired state -// and updates it if any changes are detected. Returns the updated deployment and a boolean -// indicating whether any changes were made. -func updateDeploymentIfChanged(compName, saName string, cr *argoproj.ArgoCD, deployment *appsv1.Deployment, centralTLSProfile tlsProfile.TLSConfigProfile) (*appsv1.Deployment, bool) { +// and updates it if any changes are detected. Returns the updated deployment, a boolean +// indicating whether any changes were made, and an error if desired state cannot be built. +func updateDeploymentIfChanged(compName, saName string, cr *argoproj.ArgoCD, deployment *appsv1.Deployment, centralTLSProfile tlsProfile.TLSConfigProfile) (*appsv1.Deployment, bool, error) { changed := false if !reflect.DeepEqual(deployment.Spec.Selector, buildSelector(compName, cr)) { @@ -279,7 +289,10 @@ func updateDeploymentIfChanged(compName, saName string, cr *argoproj.ArgoCD, dep changed = true deployment.Spec.Template.Spec.Containers[0].Name = generateAgentResourceName(cr.Name, compName) } - envParams := buildPrincipalContainerEnv(cr, centralTLSProfile) + envParams, err := buildPrincipalContainerEnv(cr, centralTLSProfile) + if err != nil { + return nil, false, err + } if !reflect.DeepEqual(deployment.Spec.Template.Spec.Containers[0].Env, envParams) { log.Info("deployment container env is being updated") changed = true @@ -338,11 +351,14 @@ func updateDeploymentIfChanged(compName, saName string, cr *argoproj.ArgoCD, dep deployment.Spec.Template.Spec.PriorityClassName = cr.Spec.PriorityClassName } - return deployment, changed + return deployment, changed, nil } -func buildPrincipalContainerEnv(cr *argoproj.ArgoCD, centralTLSProfile tlsProfile.TLSConfigProfile) []corev1.EnvVar { - arguments := getPrincipalTlsConfig(centralTLSProfile) +func buildPrincipalContainerEnv(cr *argoproj.ArgoCD, centralTLSProfile tlsProfile.TLSConfigProfile) ([]corev1.EnvVar, error) { + arguments, err := getPrincipalTlsConfig(centralTLSProfile) + if err != nil { + return nil, err + } env := []corev1.EnvVar{ { Name: EnvArgoCDPrincipalLogLevel, @@ -407,6 +423,9 @@ func buildPrincipalContainerEnv(cr *argoproj.ArgoCD, centralTLSProfile tlsProfil }, { Name: EnvArgoCDPrincipalCipherSuites, Value: arguments["--tlsciphers"], + }, { + Name: EnvArgoCDPrincipalTLSCurvePreferences, + Value: arguments["--tlsCurvePreferences"], }, { Name: EnvArgoCDPrincipalJwtSecretName, Value: getPrincipalJWTSecretName(cr), @@ -435,7 +454,7 @@ func buildPrincipalContainerEnv(cr *argoproj.ArgoCD, centralTLSProfile tlsProfil env = append(env, cr.Spec.ArgoCDAgent.Principal.Env...) } - return env + return env, nil } // These constants are environment variables that correspond to the environment variables @@ -469,11 +488,18 @@ const ( EnvArgoCDPrincipalEnableSelfClusterRegistration = "ARGOCD_PRINCIPAL_ENABLE_SELF_CLUSTER_REGISTRATION" EnvArgoCDPrincipalSelfRegistrationClientCertSecret = "ARGOCD_PRINCIPAL_SELF_REGISTRATION_CLIENT_CERT_SECRET" EnvArgoCDPrincipalResourceProxyAddress = "ARGOCD_PRINCIPAL_RESOURCE_PROXY_ADDRESS" + EnvArgoCDPrincipalTLSCurvePreferences = "ARGOCD_PRINCIPAL_TLS_CURVE_PREFERENCES" ) -func getPrincipalTlsConfig(centralTLSProfile tlsProfile.TLSConfigProfile) map[string]string { +// getPrincipalTlsConfig maps the cluster TLS security profile into env values for the +// argocd-agent principal process (ARGOCD_PRINCIPAL_TLS_*). Curve preferences are converted +// from OpenShift TLSGroup names to Go crypto/tls CurveID names and joined with commas, as +// expected by ARGOCD_PRINCIPAL_TLS_CURVE_PREFERENCES. Empty curve preferences mean the +// principal keeps its default behavior; a non-empty profile with no Go-supported groups +// is an error so an incompatible profile is not silently ignored. +func getPrincipalTlsConfig(centralTLSProfile tlsProfile.TLSConfigProfile) (map[string]string, error) { if centralTLSProfile.DisableClusterTLSProfile { - return nil + return nil, nil } arguments := make(map[string]string) if v := argoutil.AgentTLSProtocolVersionString(centralTLSProfile.MinVersion); v != "" { @@ -500,7 +526,17 @@ func getPrincipalTlsConfig(centralTLSProfile tlsProfile.TLSConfigProfile) map[st arguments["--tlsciphers"] = strings.Join(ciphers, ",") } } - return arguments + curves, err := argoutil.MapOpenShiftTLSGroupsToGoCurves(centralTLSProfile.CurvePreferences, "argocd-agent principal") + if err != nil { + return nil, err + } + if curves != "" { + // MapOpenShiftTLSGroupsToGoCurves joins with ":" for components that expect that + // separator; the agent principal expects a comma-separated list. + arguments["--tlsCurvePreferences"] = strings.ReplaceAll(curves, ":", ",") + } + + return arguments, nil } // Logging Configuration diff --git a/argocd-operator/controllers/argocdagent/deployment_test.go b/argocd-operator/controllers/argocdagent/deployment_test.go index f120fd342cb..9e2c414be9b 100644 --- a/argocd-operator/controllers/argocdagent/deployment_test.go +++ b/argocd-operator/controllers/argocdagent/deployment_test.go @@ -43,10 +43,18 @@ func makeTestDeployment(cr *argoproj.ArgoCD) *appsv1.Deployment { Namespace: cr.Namespace, Labels: buildLabelsForAgentPrincipal(cr.Name, testCompName), }, - Spec: buildPrincipalSpec(testCompName, generateAgentResourceName(cr.Name, testCompName), cr, tlsprofile.TLSConfigProfile{}), + Spec: mustBuildPrincipalSpec(testCompName, generateAgentResourceName(cr.Name, testCompName), cr, tlsprofile.TLSConfigProfile{}), } } +func mustBuildPrincipalSpec(compName, saName string, cr *argoproj.ArgoCD, profile tlsprofile.TLSConfigProfile) appsv1.DeploymentSpec { + spec, err := buildPrincipalSpec(compName, saName, cr, profile) + if err != nil { + panic(err) + } + return spec +} + // Helper function to create a test deployment with custom image func makeTestDeploymentWithCustomImage(cr *argoproj.ArgoCD, customImage string) *appsv1.Deployment { deployment := makeTestDeployment(cr) @@ -132,7 +140,7 @@ func TestReconcilePrincipalDeployment_DeploymentDoesNotExist_PrincipalEnabled(t assert.Equal(t, buildLabelsForAgentPrincipal(cr.Name, testCompName), deployment.Labels) // Verify Deployment has expected spec - expectedSpec := buildPrincipalSpec(testCompName, saName, cr, tlsprofile.TLSConfigProfile{}) + expectedSpec := mustBuildPrincipalSpec(testCompName, saName, cr, tlsprofile.TLSConfigProfile{}) assert.Equal(t, expectedSpec.Selector, deployment.Spec.Selector) assert.Equal(t, expectedSpec.Template.Labels, deployment.Spec.Template.Labels) assert.Equal(t, expectedSpec.Template.Spec.ServiceAccountName, deployment.Spec.Template.Spec.ServiceAccountName) @@ -143,7 +151,9 @@ func TestReconcilePrincipalDeployment_DeploymentDoesNotExist_PrincipalEnabled(t assert.Equal(t, generateAgentResourceName(cr.Name, testCompName), container.Name) assert.Equal(t, buildPrincipalImage(cr), container.Image) assert.Equal(t, buildArgs(testCompName), container.Args) - assert.Equal(t, buildPrincipalContainerEnv(cr, tlsprofile.TLSConfigProfile{}), container.Env) + env, err := buildPrincipalContainerEnv(cr, tlsprofile.TLSConfigProfile{}) + assert.NoError(t, err) + assert.Equal(t, env, container.Env) assert.Equal(t, buildSecurityContext(), container.SecurityContext) assert.Equal(t, buildPorts(testCompName), container.Ports) @@ -771,13 +781,66 @@ func TestGetPrincipalTlsConfig(t *testing.T) { }, wantErr: false, }, + { + name: "curve preferences mapped to comma-separated Go curve names", + tlsProfile: tlsprofile.TLSConfigProfile{ + MinVersion: configv1.VersionTLS12, + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupX25519, + configv1.TLSGroupSecP256r1, + configv1.TLSGroupSecP384r1, + }, + }, + expected: map[string]string{ + "--tlsminversion": "tls1.2", + "--tlsCurvePreferences": "X25519,CurveP256,CurveP384", + }, + wantErr: false, + }, + { + name: "unsupported curve groups skipped when others remain", + tlsProfile: tlsprofile.TLSConfigProfile{ + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + configv1.TLSGroupX25519, + }, + }, + expected: map[string]string{ + "--tlsCurvePreferences": "X25519", + }, + wantErr: false, + }, + { + name: "all unsupported curve groups returns error", + tlsProfile: tlsprofile.TLSConfigProfile{ + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + }, + }, + expected: nil, + wantErr: true, + }, + { + name: "empty curve preferences omit env value", + tlsProfile: tlsprofile.TLSConfigProfile{ + MinVersion: configv1.VersionTLS12, + CurvePreferences: nil, + }, + expected: map[string]string{ + "--tlsminversion": "tls1.2", + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := getPrincipalTlsConfig( - tt.tlsProfile, - ) + got, err := getPrincipalTlsConfig(tt.tlsProfile) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) if !reflect.DeepEqual(got, tt.expected) { t.Fatalf("expected %#v got %#v", tt.expected, got) } @@ -785,6 +848,30 @@ func TestGetPrincipalTlsConfig(t *testing.T) { } } +func TestBuildPrincipalContainerEnv_TLSCurvePreferences(t *testing.T) { + cr := makeTestArgoCD(withPrincipalEnabled(true)) + envVars, err := buildPrincipalContainerEnv(cr, tlsprofile.TLSConfigProfile{ + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupX25519, + configv1.TLSGroupSecP256r1, + }, + }) + assert.NoError(t, err) + + envMap := make(map[string]string) + for _, e := range envVars { + envMap[e.Name] = e.Value + } + assert.Equal(t, "X25519,CurveP256", envMap[EnvArgoCDPrincipalTLSCurvePreferences]) + + _, err = buildPrincipalContainerEnv(cr, tlsprofile.TLSConfigProfile{ + CurvePreferences: []configv1.TLSGroup{ + configv1.TLSGroupSecP256r1MLKEM768, + }, + }) + assert.Error(t, err) +} + func withPrincipalLabelSelector(selector string) argoCDOpt { return func(a *argoproj.ArgoCD) { if a.Spec.ArgoCDAgent == nil { @@ -852,7 +939,8 @@ func TestBuildPrincipalContainerEnv_LabelSelector(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - envVars := buildPrincipalContainerEnv(tt.cr, tlsprofile.TLSConfigProfile{}) + envVars, err := buildPrincipalContainerEnv(tt.cr, tlsprofile.TLSConfigProfile{}) + assert.NoError(t, err) envMap := make(map[string]string) for _, e := range envVars { envMap[e.Name] = e.Value @@ -974,7 +1062,8 @@ func TestBuildPrincipalContainerEnv_SelfRegistration(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - envVars := buildPrincipalContainerEnv(tt.cr, tlsprofile.TLSConfigProfile{}) + envVars, err := buildPrincipalContainerEnv(tt.cr, tlsprofile.TLSConfigProfile{}) + assert.NoError(t, err) envMap := make(map[string]string) for _, e := range envVars { envMap[e.Name] = e.Value diff --git a/argocd-operator/controllers/argoutil/tls.go b/argocd-operator/controllers/argoutil/tls.go index 2cd86540919..fde91f99f67 100644 --- a/argocd-operator/controllers/argoutil/tls.go +++ b/argocd-operator/controllers/argoutil/tls.go @@ -209,7 +209,10 @@ var openShiftTLSGroupToGoCurve = map[configv1.TLSGroup]string{ } // MapOpenShiftTLSGroupsToGoCurves converts OpenShift TLS groups to a -// colon-separated list of Go crypto/tls curve names (e.g. CurveP256, X25519). +// colon-separated list of Go crypto/tls curve names (e.g. CurveP256, X25519) +// for Go-based components (gitops-backend, argocd-agent principal, etc.). +// Callers that need a different separator (e.g. commas for the agent principal +// ARGOCD_PRINCIPAL_TLS_CURVE_PREFERENCES) should replace ":" as needed. // Unsupported groups are skipped and logged. If groups is non-empty but none // are supported by Go, an error is returned so callers do not treat an // incompatible profile as an absent preference.