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
18 changes: 16 additions & 2 deletions controllers/quay/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import (

// checkTLSSecurityProfile reads the cluster-wide TLS security profile from the
// OpenShift APIServer resource and populates the QuayRegistryContext with the
// corresponding SSL_PROTOCOLS and SSL_CIPHERS values. If the user has already
// set these values in config.yaml, this function is a no-op.
// corresponding SSL_PROTOCOLS, SSL_CIPHERS, SSL_CIPHERSUITES, and
// SSL_ECDH_CURVES values. If the user has already set any TLS values in
// config.yaml, this function is a no-op.
func (r *QuayRegistryReconciler) checkTLSSecurityProfile(
ctx context.Context,
qctx *quaycontext.QuayRegistryContext,
Expand All @@ -42,6 +43,9 @@ func (r *QuayRegistryReconciler) checkTLSSecurityProfile(
if _, ok := config["SSL_CIPHERSUITES"]; ok {
return nil
}
if _, ok := config["SSL_ECDH_CURVES"]; ok {
return nil
}

// Try to read the APIServer "cluster" resource.
var apiServer configv1.APIServer
Expand All @@ -63,12 +67,22 @@ func (r *QuayRegistryReconciler) checkTLSSecurityProfile(
qctx.SSLProtocols = protocols
qctx.SSLCiphers = ciphers
qctx.SSLCiphersuites = ciphersuites
qctx.SSLECDHCurves = tlsECDHCurves(apiServer.Spec.TLSSecurityProfile)
return nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// translateTLSProfile converts an OpenShift TLSSecurityProfile into
// space-separated protocol versions (nginx format) and colon-separated cipher
// names (OpenSSL format).
const modernSSLECDHCurves = "X25519MLKEM768:X25519:prime256v1"

func tlsECDHCurves(profile *configv1.TLSSecurityProfile) string {
if profile != nil && profile.Type == configv1.TLSProfileModernType {
return modernSSLECDHCurves
}
return ""
}

func translateTLSProfile(profile *configv1.TLSSecurityProfile) (protocols, ciphers, ciphersuites string) {
translate := func(spec *configv1.TLSProfileSpec) (string, string, string) {
tls12, tls13 := splitCiphers(spec.Ciphers)
Expand Down
13 changes: 13 additions & 0 deletions controllers/quay/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ func TestCheckTLSSecurityProfile_UserOverride(t *testing.T) {
name: "user set SSL_CIPHERSUITES",
configYAML: "SSL_CIPHERSUITES:\n- TLS_AES_128_GCM_SHA256",
},
{
name: "user set SSL_ECDH_CURVES",
configYAML: "SSL_ECDH_CURVES:\n- X25519",
},
} {
t.Run(tt.name, func(t *testing.T) {
qctx := quaycontext.NewQuayRegistryContext()
Expand All @@ -232,6 +236,9 @@ func TestCheckTLSSecurityProfile_UserOverride(t *testing.T) {
if qctx.SSLCiphers != "" {
t.Errorf("SSLCiphers should be empty, got %q", qctx.SSLCiphers)
}
if qctx.SSLECDHCurves != "" {
t.Errorf("SSLECDHCurves should be empty, got %q", qctx.SSLECDHCurves)
}
})
}
}
Expand Down Expand Up @@ -304,6 +311,9 @@ func TestCheckTLSSecurityProfile_WithAPIServer(t *testing.T) {
if qctx.SSLCiphersuites == "" {
t.Error("expected non-empty SSLCiphersuites for Modern profile")
}
if qctx.SSLECDHCurves != modernSSLECDHCurves {
t.Errorf("SSLECDHCurves = %q, want %q", qctx.SSLECDHCurves, modernSSLECDHCurves)
}
}

func TestCheckTLSSecurityProfile_NilProfile(t *testing.T) {
Expand Down Expand Up @@ -339,4 +349,7 @@ func TestCheckTLSSecurityProfile_NilProfile(t *testing.T) {
if qctx.SSLProtocols != "TLSv1.2 TLSv1.3" {
t.Errorf("SSLProtocols = %q, want %q", qctx.SSLProtocols, "TLSv1.2 TLSv1.3")
}
if qctx.SSLECDHCurves != "" {
t.Errorf("SSLECDHCurves should be empty for Intermediate profile, got %q", qctx.SSLECDHCurves)
}
}
1 change: 1 addition & 0 deletions pkg/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type QuayRegistryContext struct {
SSLProtocols string // e.g. "TLSv1.2 TLSv1.3" (nginx format)
SSLCiphers string // e.g. "ECDHE-RSA-AES128-GCM-SHA256:..." (TLS 1.2 ciphers, OpenSSL format)
SSLCiphersuites string // e.g. "TLS_AES_128_GCM_SHA256:..." (TLS 1.3 ciphersuites, OpenSSL format)
SSLECDHCurves string // e.g. "X25519MLKEM768:X25519:prime256v1" (OpenSSL supported groups)

// Object Storage
SupportsObjectStorage bool
Expand Down
5 changes: 5 additions & 0 deletions pkg/kustomize/kustomize.go
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,11 @@ func Inflate(
parsedUserConfig["SSL_CIPHERSUITES"] = strings.Split(ctx.SSLCiphersuites, ":")
}
}
if ctx.SSLECDHCurves != "" {
if _, ok := parsedUserConfig["SSL_ECDH_CURVES"]; !ok {
parsedUserConfig["SSL_ECDH_CURVES"] = strings.Split(ctx.SSLECDHCurves, ":")
}
}

programmaticBootstrapEnabled := ProgrammaticBootstrapEnabled(parsedUserConfig)
if programmaticBootstrapEnabled {
Expand Down
20 changes: 20 additions & 0 deletions pkg/kustomize/kustomize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,26 @@ func TestInflate(t *testing.T) {
}
}

func TestInflateInjectsSSLECDHCurves(t *testing.T) {
log := testlogr.NewTestLogger(t)
test := inflateTests[0]
test.ctx.SSLECDHCurves = "X25519MLKEM768:X25519:prime256v1"

pieces, err := Inflate(&test.ctx, test.quayRegistry, test.configBundle, log, false)
require.NoError(t, err)

for _, obj := range pieces {
objectMeta, _ := meta.Accessor(obj)
if strings.Contains(objectMeta.GetName(), configSecretPrefix) {
configBundle := obj.(*corev1.Secret)
config := decode(configBundle.Data["config.yaml"]).(map[string]interface{})
assert.Equal(t, []interface{}{"X25519MLKEM768", "X25519", "prime256v1"}, config["SSL_ECDH_CURVES"])
return
}
}
t.Fatal("generated Quay config secret not found")
}

func TestInflatePushgatewayURLInjected(t *testing.T) {
log := testlogr.NewTestLogger(t)
ctx := quaycontext.QuayRegistryContext{}
Expand Down
17 changes: 17 additions & 0 deletions test/chainsaw/tls_security_profile/chainsaw-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,20 @@ spec:
echo "FAIL: SSL_CIPHERS not found in Quay config (TLS is unmanaged, should be injected)"
exit 1
fi

if ! PROFILE_TYPE=$(kubectl get apiserver cluster \
-o jsonpath='{.spec.tlsSecurityProfile.type}'); then
echo "FAIL: unable to read apiserver cluster TLS profile"
exit 1
fi
if [ "$PROFILE_TYPE" = "Modern" ]; then
echo "$CONFIG" | grep -q "SSL_ECDH_CURVES" || {
echo "FAIL: SSL_ECDH_CURVES not found for Modern profile"
exit 1
}
echo "$CONFIG" | grep -q "X25519MLKEM768" || {
echo "FAIL: Modern profile did not inject X25519MLKEM768"
exit 1
}
echo "PASS: Modern profile injected PQC supported groups"
fi
Loading