fix: produce valid listener names for wildcard and unusual DNS names

A DNS name like *.home.hrajfrisbee.cz put "*" into the listener name; the
API server rejected the patch, which blocked every certificate on that
Gateway.

- lowercase the DNS part, spell "*" as "wildcard", map other characters
  outside [a-z0-9] to "-"
- names over 253 chars are cut and get a hash suffix instead of a blind cut
- names that were valid before are unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 20:47:44 +02:00
co-authored by Claude Opus 5.5
parent 6698eab1d8
commit 42b53b7538
5 changed files with 137 additions and 24 deletions
+60 -2
View File
@@ -1,6 +1,8 @@
package gateway
import (
"regexp"
"strings"
"testing"
"time"
@@ -272,7 +274,20 @@ func TestListenersEqual_managedFields(t *testing.T) {
}
}
func TestListenerNameFor_sanitizesDots(t *testing.T) {
// sectionNamePattern is the Gateway API SectionName validation rule.
var sectionNamePattern = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
func assertValidSectionName(t *testing.T, name string) {
t.Helper()
if len(name) > 253 {
t.Errorf("name is %d chars, limit is 253", len(name))
}
if !sectionNamePattern.MatchString(name) {
t.Errorf("name %q is not a valid SectionName", name)
}
}
func TestListenerNameFor_validSectionName(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -284,14 +299,57 @@ func TestListenerNameFor_sanitizesDots(t *testing.T) {
{name: "dots become dashes", certName: "web", dnsName: "app.example.com", want: "auto-web-app-example-com"},
{name: "single label", certName: "web", dnsName: "localhost", want: "auto-web-localhost"},
{name: "existing dashes are kept", certName: "my-cert", dnsName: "my-app.example.com", want: "auto-my-cert-my-app-example-com"},
{
name: "wildcard label is spelled out",
certName: "wildcard-home-hrajfrisbee",
dnsName: "*.home.hrajfrisbee.cz",
want: "auto-wildcard-home-hrajfrisbee-wildcard-home-hrajfrisbee-cz",
},
{name: "uppercase is lowered", certName: "web", dnsName: "App.Example.COM", want: "auto-web-app-example-com"},
{name: "other characters become dashes", certName: "web", dnsName: "my_app.example.com", want: "auto-web-my-app-example-com"},
{name: "trailing dot is dropped", certName: "web", dnsName: "app.example.com.", want: "auto-web-app-example-com"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := listenerNameFor(testPrefix, tc.certName, tc.dnsName); got != tc.want {
got := listenerNameFor(testPrefix, tc.certName, tc.dnsName)
if got != tc.want {
t.Errorf("listenerNameFor() = %q, want %q", got, tc.want)
}
assertValidSectionName(t, got)
})
}
}
func TestListenerNameFor_longNames(t *testing.T) {
t.Parallel()
// 62 dashes right where the name is cut, so a blind cut would end in "-".
shared := strings.Repeat("a", 200) + strings.Repeat("-", 62)
first := listenerNameFor(testPrefix, "web", shared+"x.example.com")
second := listenerNameFor(testPrefix, "web", shared+"y.example.com")
assertValidSectionName(t, first)
assertValidSectionName(t, second)
if first == second {
t.Errorf("two long DNS names sharing a prefix got the same listener name %q", first)
}
if again := listenerNameFor(testPrefix, "web", shared+"x.example.com"); again != first {
t.Errorf("name is not deterministic: %q then %q", first, again)
}
}
func TestBuildListenersFromCertificates_wildcard(t *testing.T) {
t.Parallel()
got := BuildListenersFromCertificates(
[]cmv1.Certificate{cert("default", "wild", "wild-tls", "*.example.com")}, testPrefix)
if len(got) != 1 {
t.Fatalf("got %d listeners, want 1", len(got))
}
assertValidSectionName(t, string(got[0].Name))
if got[0].Hostname == nil || *got[0].Hostname != "*.example.com" {
t.Errorf("hostname = %v, want the wildcard kept as *.example.com", got[0].Hostname)
}
}