Skip to content

Commit e910137

Browse files
rudrakhpshawnh2
authored andcommitted
fix: bug in overlap detection of cert SANs (envoyproxy#7234)
Signed-off-by: shawnh2 <shawnhxh@outlook.com>
1 parent c7d229c commit e910137

File tree

2 files changed

+39
-25
lines changed

2 files changed

+39
-25
lines changed

internal/gatewayapi/listener.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ func checkOverlappingHostnames(httpsListeners []*ListenerContext) {
241241
if httpsListeners[i].Port != httpsListeners[j].Port {
242242
continue
243243
}
244-
if isOverlappingHostname(httpsListeners[i].Hostname, httpsListeners[j].Hostname) {
244+
if areOverlappingHostnames(httpsListeners[i].Hostname, httpsListeners[j].Hostname) {
245245
// Overlapping listeners can be more than two, we only report the first two for simplicity.
246246
overlappingListeners[i] = &overlappingListener{
247247
gateway1: httpsListeners[i].gateway,
@@ -393,7 +393,7 @@ type overlappingCertificate struct {
393393
func isOverlappingCertificate(cert1DNSNames, cert2DNSNames []string) *overlappingCertificate {
394394
for _, dns1 := range cert1DNSNames {
395395
for _, dns2 := range cert2DNSNames {
396-
if isOverlappingHostname(ptr.To(gwapiv1.Hostname(dns1)), ptr.To(gwapiv1.Hostname(dns2))) {
396+
if areOverlappingHostnames(ptr.To(gwapiv1.Hostname(dns1)), ptr.To(gwapiv1.Hostname(dns2))) {
397397
return &overlappingCertificate{
398398
san1: dns1,
399399
san2: dns2,
@@ -404,22 +404,31 @@ func isOverlappingCertificate(cert1DNSNames, cert2DNSNames []string) *overlappin
404404
return nil
405405
}
406406

407-
// isOverlappingHostname checks if two hostnames overlap.
408-
func isOverlappingHostname(hostname1, hostname2 *gwapiv1.Hostname) bool {
409-
if hostname1 == nil || hostname2 == nil {
407+
func areOverlappingHostnames(this, other *gwapiv1.Hostname) bool {
408+
if this == nil || other == nil {
410409
return true
411410
}
412-
domain1 := strings.Replace(string(*hostname1), "*.", "", 1)
413-
domain2 := strings.Replace(string(*hostname2), "*.", "", 1)
414-
return isSubdomain(domain1, domain2) || isSubdomain(domain2, domain1)
411+
return hostnameMatchesWithOther(this, other) || hostnameMatchesWithOther(other, this)
415412
}
416413

417-
// isSubdomain checks if subDomain is a sub-domain of domain
418-
func isSubdomain(subDomain, domain string) bool {
419-
if subDomain == domain {
420-
return true
414+
// hostnameMatchesWithOther returns true if this hostname matches other hostname.
415+
// Assumes that hostnames will either be fully qualified or a wildcard hostname prefixed with a single wildcard.
416+
// E.g. "*.*.example.com" is not valid.
417+
func hostnameMatchesWithOther(this, other *gwapiv1.Hostname) bool {
418+
thisString := string(*this)
419+
otherString := string(*other)
420+
if hasWildcardPrefix(other) && !hasWildcardPrefix(this) {
421+
return strings.HasSuffix(thisString, otherString[1:]) &&
422+
!strings.Contains(strings.TrimSuffix(thisString, otherString[1:]), ".") // not a subdomain
423+
}
424+
return thisString == otherString
425+
}
426+
427+
func hasWildcardPrefix(h *gwapiv1.Hostname) bool {
428+
if h == nil {
429+
return false
421430
}
422-
return strings.HasSuffix(subDomain, fmt.Sprintf(".%s", domain))
431+
return len(string(*h)) > 1 && string(*h)[0] == '*'
423432
}
424433

425434
func buildListenerMetadata(listener *ListenerContext, gateway *GatewayContext) *ir.ResourceMetadata {

internal/gatewayapi/listener_test.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func TestProxySamplingRate(t *testing.T) {
9595
}
9696
}
9797

98-
func TestIsOverlappingHostname(t *testing.T) {
98+
func TestAreOverlappingHostnames(t *testing.T) {
9999
tests := []struct {
100100
name string
101101
hostname1 *gwapiv1.Hostname
@@ -115,10 +115,10 @@ func TestIsOverlappingHostname(t *testing.T) {
115115
want: true,
116116
},
117117
{
118-
name: "two wildcards matches subdomain",
118+
name: "two wildcards with subdomain does not match",
119119
hostname1: ptr.To(gwapiv1.Hostname("*.example.com")),
120120
hostname2: ptr.To(gwapiv1.Hostname("*.test.example.com")),
121-
want: true,
121+
want: false,
122122
},
123123
{
124124
name: "nil hostname matches all",
@@ -139,22 +139,22 @@ func TestIsOverlappingHostname(t *testing.T) {
139139
want: true,
140140
},
141141
{
142-
name: "wildcard matches exact",
142+
name: "wildcard matches exactly one level of subdomain",
143143
hostname1: ptr.To(gwapiv1.Hostname("*.example.com")),
144144
hostname2: ptr.To(gwapiv1.Hostname("test.example.com")),
145145
want: true,
146146
},
147147
{
148-
name: "wildcard matches subdomain",
148+
name: "wildcard matches only one level of subdomain",
149149
hostname1: ptr.To(gwapiv1.Hostname("*.example.com")),
150150
hostname2: ptr.To(gwapiv1.Hostname("sub.test.example.com")),
151-
want: true,
151+
want: false,
152152
},
153153
{
154-
name: "wildcard matches empty subdomain",
154+
name: "wildcard does not match empty subdomain",
155155
hostname1: ptr.To(gwapiv1.Hostname("*.example.com")),
156156
hostname2: ptr.To(gwapiv1.Hostname("example.com")),
157-
want: true,
157+
want: false,
158158
},
159159
{
160160
name: "different domains",
@@ -180,15 +180,21 @@ func TestIsOverlappingHostname(t *testing.T) {
180180
hostname2: ptr.To(gwapiv1.Hostname("testing-api.foo.dev")),
181181
want: false,
182182
},
183+
{
184+
name: "sub domain does not match with parent domain",
185+
hostname1: ptr.To(gwapiv1.Hostname("api.foo.dev")),
186+
hostname2: ptr.To(gwapiv1.Hostname("foo.dev")),
187+
want: false,
188+
},
183189
}
184190

185191
for _, tt := range tests {
186192
t.Run(tt.name, func(t *testing.T) {
187-
if got := isOverlappingHostname(tt.hostname1, tt.hostname2); got != tt.want {
193+
if got := areOverlappingHostnames(tt.hostname1, tt.hostname2); got != tt.want {
188194
t.Errorf("isOverlappingHostname(%q, %q) = %v, want %v", ptr.Deref(tt.hostname1, ""), ptr.Deref(tt.hostname2, ""), got, tt.want)
189195
}
190196
// Test should be symmetric
191-
if got := isOverlappingHostname(tt.hostname2, tt.hostname1); got != tt.want {
197+
if got := areOverlappingHostnames(tt.hostname2, tt.hostname1); got != tt.want {
192198
t.Errorf("isOverlappingHostname(%q, %q) = %v, want %v", ptr.Deref(tt.hostname2, ""), ptr.Deref(tt.hostname1, ""), got, tt.want)
193199
}
194200
})
@@ -301,15 +307,14 @@ func TestCheckOverlappingHostnames(t *testing.T) {
301307
Name: "listener-3",
302308
Protocol: gwapiv1.HTTPSProtocolType,
303309
Port: 443,
304-
Hostname: ptr.To(gwapiv1.Hostname("sub.test.example.com")),
310+
Hostname: ptr.To(gwapiv1.Hostname("sub.test.example.com")), // sub domain does not match with parent domain
305311
},
306312
},
307313
},
308314
},
309315
expected: map[int]string{
310316
0: "test.example.com",
311317
1: "*.example.com",
312-
2: "*.example.com",
313318
},
314319
},
315320
{

0 commit comments

Comments
 (0)