From 2a797798f694d602274e8729ccd43ac3f548f04c Mon Sep 17 00:00:00 2001 From: Dimitar Mavrodiev Date: Fri, 10 Jul 2026 11:17:27 +0300 Subject: [PATCH 1/3] fix: dedupe SAN entries before checking algorithm uniqueness Dedupe the certificate's domains with sets.New before checking them against the shared pkaSecretSet map, so a repeated SAN no longer conflicts with itself, while a genuine conflict across two different certificates for the same domain and algorithm is still caught. Add test cases to TestValidateTLSSecretsData: - valid-rsa-duplicate-san-domain: reproduces the bug against a new rsa-cert-dup-san.pem / rsa-pkcs8-dup-san.key fixture. - conflicting-rsa-algorithm-same-domain-different-secrets: guard rail confirming genuine cross-secret conflicts are still rejected. Fixes #9471 Signed-off-by: Dimitar Mavrodiev --- internal/gatewayapi/testdata/tls/gen-certs.sh | 4 +++ .../testdata/tls/rsa-cert-dup-san.pem | 19 +++++++++++++ .../testdata/tls/rsa-pkcs8-dup-san.key | 28 +++++++++++++++++++ internal/gatewayapi/tls.go | 2 +- internal/gatewayapi/tls_test.go | 20 +++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 internal/gatewayapi/testdata/tls/rsa-cert-dup-san.pem create mode 100644 internal/gatewayapi/testdata/tls/rsa-pkcs8-dup-san.key diff --git a/internal/gatewayapi/testdata/tls/gen-certs.sh b/internal/gatewayapi/testdata/tls/gen-certs.sh index 8d428ed4a3..d6637d9048 100755 --- a/internal/gatewayapi/testdata/tls/gen-certs.sh +++ b/internal/gatewayapi/testdata/tls/gen-certs.sh @@ -20,6 +20,10 @@ openssl rsa -in rsa-pkcs8-san.key -out rsa-pkcs1-san.key openssl req -x509 -nodes -days $CERT_VALIDITY_DAYS -newkey rsa:2048 -keyout rsa-pkcs8-wildcard.key -out rsa-cert-wildcard.pem -subj "/CN=Test Inc" -addext "subjectAltName = DNS:*, DNS:*.example.com" openssl rsa -in rsa-pkcs8-wildcard.key -out rsa-pkcs1-wildcard.key +# RSA with a duplicate SAN entry + +openssl req -x509 -nodes -days $CERT_VALIDITY_DAYS -newkey rsa:2048 -keyout rsa-pkcs8-dup-san.key -out rsa-cert-dup-san.pem -subj "/CN=Test Inc" -addext "subjectAltName = DNS:foo.bar.com, DNS:foo.bar.com" + # ECDSA-p256 openssl ecparam -name prime256v1 -genkey -noout -out ecdsa-p256.key diff --git a/internal/gatewayapi/testdata/tls/rsa-cert-dup-san.pem b/internal/gatewayapi/testdata/tls/rsa-cert-dup-san.pem new file mode 100644 index 0000000000..383cbb4e92 --- /dev/null +++ b/internal/gatewayapi/testdata/tls/rsa-cert-dup-san.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDLDCCAhSgAwIBAgIUVc3BBxB88/3dXu5GNXH/Rmz+kNkwDQYJKoZIhvcNAQEL +BQAwEzERMA8GA1UEAwwIVGVzdCBJbmMwHhcNMjYwNzA5MTkyOTM0WhcNMzYwNzA2 +MTkyOTM0WjATMREwDwYDVQQDDAhUZXN0IEluYzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAJCEi+CScI2LitW3BMDNUOM+JPZT3wtaUsOFNxElz2S+5nQR +s9jx63wAgNHDtsgMvQs+Xq9IC5quyn/TIWme3G7/9X8KsflNAsbbBCGKA0BeAc7n +r25SZiWxY06hEeVnzxWcJdlR4eLZSzJihS8JQZmM6HVyr86P75B5jQP7+HFfp/LA +JvNaILNsM0UjbnNqYxLGOEANZw5u56WjXUbhwySCybTWl4o3y0fWsvtSq4vIO1XK +pU1QgDbLAVDNz3E4CXajD+gGcid4m2wzqVMiCvb7SwBa7a8TdipoQbwXaldh4l64 +o+FNVyI8Sp3ZMsNGoUq9oq/cU6O4MIwbFC/xqT8CAwEAAaN4MHYwHQYDVR0OBBYE +FK6GC7CbB5XjcnxLI9Lzvs1CBz/MMB8GA1UdIwQYMBaAFK6GC7CbB5XjcnxLI9Lz +vs1CBz/MMA8GA1UdEwEB/wQFMAMBAf8wIwYDVR0RBBwwGoILZm9vLmJhci5jb22C +C2Zvby5iYXIuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBy24sXi/E3O83bwUogTan7 +2FV3EoFH2Y++uSeIDmtMxpZ/0NLWP57FohXRK/RV2aFp0uTcd8jBlf9elhYrPu9j +UzlPbn8NygS11CrVGaJQ+2jHapmh4jfVjqkbB+VesCXXF9cF0sWkUcTthcaeYVYY +4yThNZhovxN1n7lqmG7E3bsAV3//mSA+NQkdJ5FBUx4S7G5go9vHNUaUtc95lfwh +GcRxMypBFXyZrgOdlWROIFzm9TZnvqBZDnv7gBtlkB/BqwJ2W8sSavVyNeY81MF0 +d0iuqmgR9+w1WjghDftCgEDh60a8dWO8NfBMx0N/OLpm/VRUOAKLlbA5Qg2TONhp +-----END CERTIFICATE----- diff --git a/internal/gatewayapi/testdata/tls/rsa-pkcs8-dup-san.key b/internal/gatewayapi/testdata/tls/rsa-pkcs8-dup-san.key new file mode 100644 index 0000000000..5d08cbfda2 --- /dev/null +++ b/internal/gatewayapi/testdata/tls/rsa-pkcs8-dup-san.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCQhIvgknCNi4rV +twTAzVDjPiT2U98LWlLDhTcRJc9kvuZ0EbPY8et8AIDRw7bIDL0LPl6vSAuarsp/ +0yFpntxu//V/CrH5TQLG2wQhigNAXgHO569uUmYlsWNOoRHlZ88VnCXZUeHi2Usy +YoUvCUGZjOh1cq/Oj++QeY0D+/hxX6fywCbzWiCzbDNFI25zamMSxjhADWcObuel +o11G4cMkgsm01peKN8tH1rL7UquLyDtVyqVNUIA2ywFQzc9xOAl2ow/oBnIneJts +M6lTIgr2+0sAWu2vE3YqaEG8F2pXYeJeuKPhTVciPEqd2TLDRqFKvaKv3FOjuDCM +GxQv8ak/AgMBAAECggEAL/HxcA9VTPhbFp0R9B8Js2JmI9zedidApwI2qzc2j49v +6FkJKDPWcry+ABmktcjYHPdTtWY7B1Xu86ppft+H9UFwwnWbZwCgJ7X4sGHXw06M +3gZqYrjuj5nCvw7b35ZpkxtLSUaLoNWDR5N86QZyn40qf/CNGAQTsARLfuNk4MOi +SNM/f8P23JgruBa2ceIC0r3BXQiUrKHwmIwSmEh2/x4jEhZ9+E5XOFpq3AoqVM8h +Ccspyh61nJs+uY87mbE717XFq1Yq0XwotlbEgKzH8v9Uus1Et9GDboinkE/u9Eh4 +PUnDT/ya5yijN8FDms0ifAVbJnuQmxU4ZP5XeCr4hQKBgQDFz8LCmA+f2bYhyEL5 +2zNJgW78gbJnwyiZpcdEcv8KATbeq+2tha0piiph2sVQszxjswZav7E2htI3D4d2 +wEHmVf5cV7qG46BK1nFT3TTnbWP72byr/6cg1BEhpNYgqnpb4n8bGtASwQGt/ZpT +qmGiLbV7PdbSPJ/Z3O6cVQ1K1QKBgQC7B3wmIsUAFl2QZIdRk7rTsOCAWSm4SZDn +NgffJ1EVyzTcr3FpVZLZkN1oJWulnLjeWpg2r9UEPVWSkkIQjUCaQmABpI/Uo59N +ScrZMlnarPOUqf0S40SduW5HMvu2B9TT4rmMPWpUY7sBOEqnN1t9DFOAOv+Ndq+m +F7nAua2FwwKBgDMbLlJgPwkpkmi/+K3c+C8xhZ8vUwyD22V28zi4DTRkg+ybtthy +BP8Kd1C42Om0pRGNG0Mu63YO9xjKplED8wKzjPgGomZfQPaU2Mq2CAkSthZHdvtp +HaDZqWNr1vaxlNNQfU5fawqtWuW887ZR+s+Px6eDnpDKoPIEppE1WC3RAoGAXya5 +tLUvwJGgXFuotIoSHKz6KpIyNX3H6LmGW7Om/w15AWWIr2xH38RhwCB5mbIYI5e3 +pOrj1tpVdNJQJheW7GQkb/GG80mjPDD0sHd7W1NuQQ4SoM9bE1tJjZOUl9F4J6xL +dduxAuoSM9attFDnjMD+olhht1jQmBGuASz16P0CgYBUj5F+va6MiZX91QzS3Jgf +x7mxuuTPIs1kWMqiqZNMw3x3/FBQpgxYJtYy2si0T7y19WSeEcHU/h7Zret0tUEN +zAomDKPWzQeF+lfpArUGN4A0WoMfVe0Z8HFHzbFakKfM9DpxpSvcA0Suocs2oZ86 +6S6A/ISFxW5kdRLPcpCMmA== +-----END PRIVATE KEY----- diff --git a/internal/gatewayapi/tls.go b/internal/gatewayapi/tls.go index 8fb5cc86b4..7fa009b90e 100644 --- a/internal/gatewayapi/tls.go +++ b/internal/gatewayapi/tls.go @@ -126,7 +126,7 @@ func parseCertsFromTLSSecretsData(secrets []*corev1.Secret) ([]*corev1.Secret, [ // Check uniqueness for each domain in the certificate with this algorithm hasConflictDomainAlgorithm := false - for _, domain := range certDomains { + for domain := range sets.New(certDomains...) { pkaSecretKey := fmt.Sprintf("%s/%s", publicKeyAlgorithm, domain) // Check whether the public key algorithm and certificate domain are unique diff --git a/internal/gatewayapi/tls_test.go b/internal/gatewayapi/tls_test.go index 1390675879..c4fdae628c 100644 --- a/internal/gatewayapi/tls_test.go +++ b/internal/gatewayapi/tls_test.go @@ -176,6 +176,26 @@ func TestValidateTLSSecretsData(t *testing.T) { ExpectedValidCount: 1, ExpectedCertsCount: 1, }, + { + Name: "valid-rsa-duplicate-san-domain", + CertFiles: []string{"rsa-cert-dup-san.pem"}, + KeyFiles: []string{"rsa-pkcs8-dup-san.key"}, + ExpectedErrMsg: "", + ExpectedErrReason: "", + ExpectedValidCount: 1, + ExpectedCertsCount: 1, + }, + { + // Two distinct secrets legitimately claiming the same domain with the + // same public key algorithm must still be rejected. + Name: "conflicting-rsa-algorithm-same-domain-different-secrets", + CertFiles: []string{"rsa-cert.pem", "rsa-cert.pem"}, + KeyFiles: []string{"rsa-pkcs1.key", "rsa-pkcs1.key"}, + ExpectedErrMsg: "test/secret public key algorithm must be unique, certificate domain foo.bar.com has a conflicting algorithm [RSA]", + ExpectedErrReason: status.ListenerReasonPartiallyInvalidCertificateRef, + ExpectedValidCount: 1, + ExpectedCertsCount: 1, + }, { Name: "valid-ecdsa-p256", CertFiles: []string{"ecdsa-p256-cert.pem"}, From dff7abe899d39e74f821cfa662fe277801c3d154 Mon Sep 17 00:00:00 2001 From: Dimitar Mavrodiev Date: Fri, 10 Jul 2026 17:40:02 +0300 Subject: [PATCH 2/3] Apply codex review comment Signed-off-by: Dimitar Mavrodiev --- internal/gatewayapi/tls.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/gatewayapi/tls.go b/internal/gatewayapi/tls.go index 7fa009b90e..2b3c968dd7 100644 --- a/internal/gatewayapi/tls.go +++ b/internal/gatewayapi/tls.go @@ -125,8 +125,14 @@ func parseCertsFromTLSSecretsData(secrets []*corev1.Secret) ([]*corev1.Secret, [ } // Check uniqueness for each domain in the certificate with this algorithm + seenDomains := sets.New[string]() hasConflictDomainAlgorithm := false - for domain := range sets.New(certDomains...) { + for _, domain := range certDomains { + if seenDomains.Has(domain) { + continue + } + seenDomains.Insert(domain) + pkaSecretKey := fmt.Sprintf("%s/%s", publicKeyAlgorithm, domain) // Check whether the public key algorithm and certificate domain are unique From 8a37916be8d0e491d54e41b73d92894ecc2c8ded Mon Sep 17 00:00:00 2001 From: Dimitar Mavrodiev Date: Mon, 13 Jul 2026 10:18:09 +0300 Subject: [PATCH 3/3] fix: address code review comment Link the SANs RFC section in the comments. Signed-off-by: Dimitar Mavrodiev --- internal/gatewayapi/tls.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/gatewayapi/tls.go b/internal/gatewayapi/tls.go index 2b3c968dd7..1a5d9e3820 100644 --- a/internal/gatewayapi/tls.go +++ b/internal/gatewayapi/tls.go @@ -125,6 +125,8 @@ func parseCertsFromTLSSecretsData(secrets []*corev1.Secret) ([]*corev1.Secret, [ } // Check uniqueness for each domain in the certificate with this algorithm + // Dedupe SANs within this single cert first - RFC 5280 4.2.1.6 permits + // repeated entries in a GeneralNames sequence seenDomains := sets.New[string]() hasConflictDomainAlgorithm := false for _, domain := range certDomains {