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
55 changes: 45 additions & 10 deletions internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,30 @@ func computeHosts(routeHostnames []string, listenerContext *ListenerContext) []s
case listenerHostnameVal == routeHostname:
hostnamesSet.Insert(routeHostname)

// Both listener and route hostname have wildcards:
case strings.HasPrefix(listenerHostnameVal, "*") && strings.HasPrefix(routeHostname, "*"):
// the route hostname must be more wildcard than the listener hostname to match
// e.g. listener hostname *.example.com would match route hostname *.com.
// use regex to check this
if wildcardHostnameMatchesHostname(routeHostname, listenerHostnameVal) {
hostnamesSet.Insert(listenerHostnameVal)
}

if wildcardHostnameMatchesHostname(listenerHostnameVal, routeHostname) {
hostnamesSet.Insert(routeHostname)
}

// Listener has a wildcard hostname: check if the route hostname matches.
case strings.HasPrefix(listenerHostnameVal, "*"):
if hostnameMatchesWildcardHostname(routeHostname, listenerHostnameVal) {
if wildcardHostnameMatchesHostname(listenerHostnameVal, routeHostname) {
hostnamesSet.Insert(routeHostname)
}

// Route has a wildcard hostname: check if the listener hostname matches.
case strings.HasPrefix(routeHostname, "*"):
if hostnameMatchesWildcardHostname(listenerHostnameVal, routeHostname) {
if wildcardHostnameMatchesHostname(routeHostname, listenerHostnameVal) {
hostnamesSet.Insert(listenerHostnameVal)
}

}
}

Expand All @@ -370,16 +382,39 @@ func computeHosts(routeHostnames []string, listenerContext *ListenerContext) []s
return hostnamesSet.List()
}

// hostnameMatchesWildcardHostname returns true if hostname has the non-wildcard
// portion of wildcardHostname as a suffix, plus at least one DNS label matching the
// wildcard.
func hostnameMatchesWildcardHostname(hostname, wildcardHostname string) bool {
if !strings.HasSuffix(hostname, strings.TrimPrefix(wildcardHostname, "*")) {
// wildcardHostnameMatchesHostname returns true if wildcardHostname matches hostname.
// ref: https://github.com/kubernetes-sigs/gateway-api/pull/1173, this's different with RFC-2818
// e.g. *.com matches *.example.com, *.example.com matches foo.example.com
func wildcardHostnameMatchesHostname(wildcardHostname, hostname string) bool {
// Strip the leading "*" from wildcardHostname
wildcardSuffix := strings.TrimPrefix(wildcardHostname, "*")

// If hostname is not a wildcard, check if it matches the pattern
if !strings.HasPrefix(hostname, "*") {
// hostname must end with the wildcard suffix
if !strings.HasSuffix(hostname, wildcardSuffix) {
return false
}
// The part before the suffix should be non-empty (there's a label matching the wildcard)
wildcardMatch := strings.TrimSuffix(hostname, wildcardSuffix)
return len(wildcardMatch) > 0
}

// Both are wildcards - strip the leading "*" from hostname too
hostnameSuffix := strings.TrimPrefix(hostname, "*")

// Check if the hostname suffix ends with the wildcard suffix
// This means wildcardHostname is a broader pattern
if !strings.HasSuffix(hostnameSuffix, wildcardSuffix) {
return false
}

wildcardMatch := strings.TrimSuffix(hostname, strings.TrimPrefix(wildcardHostname, "*"))
return len(wildcardMatch) > 0
// Get the remaining part after removing the wildcard suffix from hostname
remaining := strings.TrimSuffix(hostnameSuffix, wildcardSuffix)

// The remaining part should have content (can't be identical patterns)
// and should start with "." to be a valid subdomain
return len(remaining) > 0 && strings.HasPrefix(remaining, ".")
}

func containsPort(ports []*protocolPort, port *protocolPort) bool {
Expand Down
53 changes: 53 additions & 0 deletions internal/gatewayapi/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -895,3 +895,56 @@ func TestIrStringMatch(t *testing.T) {
})
}
}

func TestWildcardHostnameMatchesHostname(t *testing.T) {
testCases := []struct {
name string
wildcard string
hostname string
expected bool
}{
{
name: "*.com matches *.example.com",
wildcard: "*.com",
hostname: "*.example.com",
expected: true,
},
{
name: "*.example.com matches *.foo.example.com",
wildcard: "*.example.com",
hostname: "*.foo.example.com",
expected: true,
},
{
name: "*.com does not match *.net",
wildcard: "*.com",
hostname: "*.net",
expected: false,
},
{
name: "*.example.com does not match *.other.com",
wildcard: "*.example.com",
hostname: "*.other.com",
expected: false,
},
{
name: "*.foo.example.com does not match *.example.com",
wildcard: "*.foo.example.com",
hostname: "*.example.com",
expected: false,
},
{
name: "*.example.com match foo.example.com",
wildcard: "*.example.com",
hostname: "foo.example.com",
expected: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := wildcardHostnameMatchesHostname(tc.wildcard, tc.hostname)
require.Equal(t, tc.expected, result)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: tls
protocol: TLS
hostname: "*.example.com"
port: 90
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: All
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-2
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: tls
protocol: TLS
hostname: "*.com"
port: 90
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: All
tlsRoutes:
- apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
namespace: default
name: tlsroute-1
spec:
parentRefs:
- namespace: envoy-gateway
name: gateway-1
hostnames:
- "*.com"
rules:
- backendRefs:
- name: service-1
port: 8080
- apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
namespace: default
name: tlsroute-2
spec:
parentRefs:
- namespace: envoy-gateway
name: gateway-2
hostnames:
- "*.example.com"
rules:
- backendRefs:
- name: service-1
port: 8080
Loading