Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions argocd-operator/build/redis/haproxy.cfg.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ global
ssl-default-server-ciphersuites {{.TLSCiphers}}
{{- end}}
{{- end}}

{{- if .TLSCurves}}
ssl-default-bind-curves {{.TLSCurves}}
ssl-default-server-curves {{.TLSCurves}}
{{- end}}
{{- end}}

defaults REDIS
Expand Down
5 changes: 3 additions & 2 deletions argocd-operator/common/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,11 @@ const (
ArgoCDDefaultRedisHAReplicas = int32(3)

// ArgoCDDefaultRedisHAProxyImage is the default Redis HAProxy image to use when not specified.
ArgoCDDefaultRedisHAProxyImage = "public.ecr.aws/docker/library/haproxy"
// haproxytech builds use AWS-LC, which supports post-quantum TLS groups (e.g. X25519MLKEM768).
ArgoCDDefaultRedisHAProxyImage = "docker.io/haproxytech/haproxy-alpine"

// ArgoCDDefaultRedisHAProxyVersion is the default Redis HAProxy image tag to use when not specified.
ArgoCDDefaultRedisHAProxyVersion = "sha256:e11f034e651603f10a365e5ad5a0321825e18eded9620e40c4f4d6ae58419bfe" // 3.0.8-alpine
ArgoCDDefaultRedisHAProxyVersion = "sha256:4b2da4adb652487a1aa1bb7fdacf5df1088d2c7d21302fcce738812d0d2fc453" // 3.3.6

// ArgoCDDefaultRedisImage is the Redis container image to use when not specified.
ArgoCDDefaultRedisImage = "public.ecr.aws/docker/library/redis"
Expand Down
38 changes: 38 additions & 0 deletions argocd-operator/controllers/argoutil/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,40 @@ func GetRedisInitScript(cr *argoproj.ArgoCD, useTLSForRedis bool) string {
return script
}

// haproxySupportedCurves maps OpenShift TLS group names to HAProxy/AWS-LC curve names.
// Classical groups use NIST aliases (secp256r1 -> P-256). Post-quantum hybrid groups
// are passed through for HAProxy 3.3+ with AWS-LC.
var haproxySupportedCurves = map[string]string{
// Classical curves (OpenShift IANA name -> HAProxy NIST name)
"X25519": "X25519",
"secp256r1": "P-256",
"secp384r1": "P-384",
"secp521r1": "P-521",
"P-256": "P-256",
"P-384": "P-384",
"P-521": "P-521",
// Post-quantum hybrid groups (same name in OpenShift and HAProxy/AWS-LC)
"X25519MLKEM768": "X25519MLKEM768",
"SecP256r1MLKEM768": "SecP256r1MLKEM768",
"SecP384r1MLKEM1024": "SecP384r1MLKEM1024",
}

// MapCurvePreferencesToHAProxyCurves converts OpenShift TLS group names to a
// colon-separated HAProxy curves list. Classical OpenShift names are converted
// to NIST aliases (e.g. secp256r1 -> P-256). Unknown groups are skipped.
func MapCurvePreferencesToHAProxyCurves(groups []string) string {
if len(groups) == 0 {
return ""
}
curves := make([]string, 0, len(groups))
for _, group := range groups {
if curve, ok := haproxySupportedCurves[group]; ok {
curves = append(curves, curve)
}
}
return strings.Join(curves, ":")
}

// GetRedisHAProxyConfig will load the Redis HA Proxy configuration from a template on disk for the given ArgoCD.
// If an error occurs, an empty string value will be returned.
func GetRedisHAProxyConfig(cr *argoproj.ArgoCD, useTLSForRedis bool, centralTLSConfigProfile tlsprofile.TLSConfigProfile) string {
Expand All @@ -203,6 +237,10 @@ func GetRedisHAProxyConfig(cr *argoproj.ArgoCD, useTLSForRedis bool, centralTLSC
vars["TLSCiphers"] = strings.Join(centralTLSConfigProfile.Ciphers, ":")
}

if curves := MapCurvePreferencesToHAProxyCurves(centralTLSConfigProfile.CurvePreferences); curves != "" {
vars["TLSCurves"] = curves
}

script, err := loadTemplateFile(path, vars)
if err != nil {
log.Error(err, "unable to load redis haproxy configuration")
Expand Down
107 changes: 105 additions & 2 deletions argocd-operator/controllers/argoutil/redis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"github.com/argoproj-labs/gitops-operator/argocd-operator/pkg/tlsprofile"
)

// TestGetRedisHAProxyConfigRenderedTLSValues verifies that TLS minVersion and ciphers
// are correctly rendered in the final HAProxy configuration template output.
// TestGetRedisHAProxyConfigRenderedTLSValues verifies that TLS minVersion, ciphers,
// and curve preferences are correctly rendered in the final HAProxy configuration.
func TestGetRedisHAProxyConfigRenderedTLSValues(t *testing.T) {
wd, err := os.Getwd()
require.NoError(t, err)
Expand Down Expand Up @@ -50,6 +50,10 @@ func TestGetRedisHAProxyConfigRenderedTLSValues(t *testing.T) {
"ssl-default-bind-ciphersuites ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"ssl-default-server-ciphersuites ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
},
notExpectedInOutput: []string{
"ssl-default-bind-curves",
"ssl-default-server-curves",
},
validatePattern: regexp.MustCompile(`ssl-min-ver\s+TLSv1\.2`),
},
{
Expand Down Expand Up @@ -90,6 +94,62 @@ func TestGetRedisHAProxyConfigRenderedTLSValues(t *testing.T) {
"ssl-default-server-ciphers ",
"ssl-default-bind-ciphersuites",
"ssl-default-server-ciphersuites",
"ssl-default-bind-curves",
"ssl-default-server-curves",
},
},
{
name: "TLS with curve preferences maps OpenShift groups to HAProxy names",
useTLS: true,
centralTLSConfigProfile: tlsprofile.TLSConfigProfile{
MinVersion: configv1.VersionTLS12,
CurvePreferences: []string{
"X25519MLKEM768",
"X25519",
"secp256r1",
"secp384r1",
},
},
expectedInOutput: []string{
"ssl-default-bind-options ssl-min-ver TLSv1.2",
"ssl-default-server-options ssl-min-ver TLSv1.2",
"ssl-default-bind-curves X25519MLKEM768:X25519:P-256:P-384",
"ssl-default-server-curves X25519MLKEM768:X25519:P-256:P-384",
},
notExpectedInOutput: []string{
"ssl-default-bind-ciphers ",
"ssl-default-server-ciphers ",
"ssl-default-bind-ciphersuites",
"ssl-default-server-ciphersuites",
"secp256r1",
"secp384r1",
},
},
{
name: "TLS 1.3 with ciphers and curve preferences",
useTLS: true,
centralTLSConfigProfile: tlsprofile.TLSConfigProfile{
MinVersion: configv1.VersionTLS13,
Ciphers: []string{
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
},
CurvePreferences: []string{
"X25519",
"secp256r1",
},
},
expectedInOutput: []string{
"ssl-default-bind-options ssl-min-ver TLSv1.3",
"ssl-default-server-options ssl-min-ver TLSv1.3",
"ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384",
"ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384",
"ssl-default-bind-curves X25519:P-256",
"ssl-default-server-curves X25519:P-256",
},
notExpectedInOutput: []string{
"ssl-default-bind-ciphers ",
"ssl-default-server-ciphers ",
},
},
{
Expand All @@ -104,6 +164,8 @@ func TestGetRedisHAProxyConfigRenderedTLSValues(t *testing.T) {
"ssl-default-server-ciphers",
"ssl-default-bind-ciphersuites",
"ssl-default-server-ciphersuites",
"ssl-default-bind-curves",
"ssl-default-server-curves",
},
},
}
Expand Down Expand Up @@ -151,7 +213,48 @@ func TestGetRedisHAProxyConfigRenderedTLSValues(t *testing.T) {
} else {
assert.Empty(t, capturedVars["TLSCiphers"])
}
expectedCurves := MapCurvePreferencesToHAProxyCurves(tt.centralTLSConfigProfile.CurvePreferences)
if expectedCurves != "" {
assert.Equal(t, expectedCurves, capturedVars["TLSCurves"])
} else {
assert.Empty(t, capturedVars["TLSCurves"])
}
}
})
}
}

func TestMapCurvePreferencesToHAProxyCurves(t *testing.T) {
tests := []struct {
name string
input []string
expected string
}{
{
name: "empty",
input: nil,
expected: "",
},
{
name: "classical OpenShift groups",
input: []string{"X25519", "secp256r1", "secp384r1", "secp521r1"},
expected: "X25519:P-256:P-384:P-521",
},
{
name: "maps OpenShift groups including PQC",
input: []string{"X25519MLKEM768", "X25519", "SecP256r1MLKEM768", "secp256r1"},
expected: "X25519MLKEM768:X25519:SecP256r1MLKEM768:P-256",
},
{
name: "skips unknown groups",
input: []string{"UnknownGroup", "X25519"},
expected: "X25519",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, MapCurvePreferencesToHAProxyCurves(tt.input))
})
}
}
2 changes: 2 additions & 0 deletions argocd-operator/pkg/tlsprofile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
5 changes: 5 additions & 0 deletions build/redis/haproxy.cfg.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ global
ssl-default-server-ciphersuites {{.TLSCiphers}}
{{- end}}
{{- end}}

{{- if .TLSCurves}}
ssl-default-bind-curves {{.TLSCurves}}
ssl-default-server-curves {{.TLSCurves}}
{{- end}}
{{- end}}

defaults REDIS
Expand Down
6 changes: 6 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,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(),
Expand All @@ -382,6 +387,7 @@ func main() {
DisableClusterTLSProfile: disableClusterTLSProfile,
MinVersion: profile.MinTLSVersion,
Ciphers: profile.Ciphers,
CurvePreferences: curvePreferences,
},
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Argo CD")
Expand Down
Loading