Support HostSNIRegexp in GatewayAPI TLS routes
This commit is contained in:
parent
3eeea2bb2b
commit
a08a428787
3 changed files with 45 additions and 24 deletions
|
@ -39,7 +39,7 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
hostnames:
|
hostnames:
|
||||||
- "*.foo.bar"
|
- "*.foo.*.bar"
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- backendRefs:
|
||||||
- name: whoamitcp
|
- name: whoamitcp
|
||||||
|
|
|
@ -839,7 +839,7 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener v1alpha2.
|
||||||
}
|
}
|
||||||
|
|
||||||
router := dynamic.TCPRouter{
|
router := dynamic.TCPRouter{
|
||||||
Rule: "HostSNI(`*`)", // Gateway listener hostname not available in TCP
|
Rule: "HostSNI(`*`)",
|
||||||
EntryPoints: []string{ep},
|
EntryPoints: []string{ep},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,8 +970,16 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener v1alpha2.
|
||||||
|
|
||||||
hostnames := matchingHostnames(listener, route.Spec.Hostnames)
|
hostnames := matchingHostnames(listener, route.Spec.Hostnames)
|
||||||
if len(hostnames) == 0 && listener.Hostname != nil && *listener.Hostname != "" && len(route.Spec.Hostnames) > 0 {
|
if len(hostnames) == 0 && listener.Hostname != nil && *listener.Hostname != "" && len(route.Spec.Hostnames) > 0 {
|
||||||
// TODO update the corresponding route parent status
|
for _, parent := range route.Status.Parents {
|
||||||
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TLSRoute
|
parent.Conditions = append(parent.Conditions, metav1.Condition{
|
||||||
|
Type: string(v1alpha2.GatewayClassConditionStatusAccepted),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
Reason: string(v1alpha2.ListenerReasonRouteConflict),
|
||||||
|
Message: fmt.Sprintf("No hostname match between listener: %v and route: %v", listener.Hostname, route.Spec.Hostnames),
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1207,7 +1215,7 @@ func hostRule(hostnames []v1alpha2.Hostname) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostSNIRule(hostnames []v1alpha2.Hostname) (string, error) {
|
func hostSNIRule(hostnames []v1alpha2.Hostname) (string, error) {
|
||||||
var matchers []string
|
rules := make([]string, 0, len(hostnames))
|
||||||
uniqHostnames := map[v1alpha2.Hostname]struct{}{}
|
uniqHostnames := map[v1alpha2.Hostname]struct{}{}
|
||||||
|
|
||||||
for _, hostname := range hostnames {
|
for _, hostname := range hostnames {
|
||||||
|
@ -1219,25 +1227,28 @@ func hostSNIRule(hostnames []v1alpha2.Hostname) (string, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
h := string(hostname)
|
host := string(hostname)
|
||||||
|
|
||||||
// TODO support wildcard hostnames with an HostSNI regexp matcher
|
|
||||||
if strings.Contains(h, "*") {
|
|
||||||
return "", fmt.Errorf("wildcard hostname is not supported: %q", h)
|
|
||||||
}
|
|
||||||
|
|
||||||
matchers = append(matchers, fmt.Sprintf("HostSNI(`%s`)", h))
|
|
||||||
uniqHostnames[hostname] = struct{}{}
|
uniqHostnames[hostname] = struct{}{}
|
||||||
|
|
||||||
|
wildcard := strings.Count(host, "*")
|
||||||
|
if wildcard == 0 {
|
||||||
|
rules = append(rules, fmt.Sprintf("HostSNI(`%s`)", host))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch len(matchers) {
|
if !strings.HasPrefix(host, "*.") || wildcard > 1 {
|
||||||
case 0:
|
return "", fmt.Errorf("invalid rule: %q", host)
|
||||||
return "HostSNI(`*`)", nil
|
|
||||||
case 1:
|
|
||||||
return matchers[0], nil
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("(%s)", strings.Join(matchers, " || ")), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-zA-Z0-9-]+\.`, 1)
|
||||||
|
rules = append(rules, fmt.Sprintf("HostSNIRegexp(`^%s$`)", host))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hostnames) == 0 || len(rules) == 0 {
|
||||||
|
return "HostSNI(`*`)", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(rules, " || "), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractRule(routeRule v1alpha2.HTTPRouteRule, hostRule string) (string, error) {
|
func extractRule(routeRule v1alpha2.HTTPRouteRule, hostRule string) (string, error) {
|
||||||
|
|
|
@ -3076,10 +3076,10 @@ func TestLoadTLSRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
TCP: &dynamic.TCPConfiguration{
|
TCP: &dynamic.TCPConfiguration{
|
||||||
Routers: map[string]*dynamic.TCPRouter{
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
"default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7": {
|
"default-tls-app-1-my-gateway-tls-d5342d75658583f03593": {
|
||||||
EntryPoints: []string{"tls"},
|
EntryPoints: []string{"tls"},
|
||||||
Service: "default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7-wrr-0",
|
Service: "default-tls-app-1-my-gateway-tls-d5342d75658583f03593-wrr-0",
|
||||||
Rule: "(HostSNI(`foo.example.com`) || HostSNI(`bar.example.com`))",
|
Rule: "HostSNI(`foo.example.com`) || HostSNI(`bar.example.com`)",
|
||||||
TLS: &dynamic.RouterTCPTLSConfig{
|
TLS: &dynamic.RouterTCPTLSConfig{
|
||||||
Passthrough: true,
|
Passthrough: true,
|
||||||
},
|
},
|
||||||
|
@ -3087,7 +3087,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
Services: map[string]*dynamic.TCPService{
|
Services: map[string]*dynamic.TCPService{
|
||||||
"default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7-wrr-0": {
|
"default-tls-app-1-my-gateway-tls-d5342d75658583f03593-wrr-0": {
|
||||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||||
Services: []dynamic.TCPWRRService{
|
Services: []dynamic.TCPWRRService{
|
||||||
{
|
{
|
||||||
|
@ -4780,6 +4780,11 @@ func Test_hostSNIRule(t *testing.T) {
|
||||||
hostnames: []v1alpha2.Hostname{"*"},
|
hostnames: []v1alpha2.Hostname{"*"},
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Supported wildcard",
|
||||||
|
hostnames: []v1alpha2.Hostname{"*.foo"},
|
||||||
|
expectedRule: "HostSNIRegexp(`^[a-zA-Z0-9-]+\\.foo$`)",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Multiple malformed wildcard",
|
desc: "Multiple malformed wildcard",
|
||||||
hostnames: []v1alpha2.Hostname{"*.foo.*"},
|
hostnames: []v1alpha2.Hostname{"*.foo.*"},
|
||||||
|
@ -4788,7 +4793,7 @@ func Test_hostSNIRule(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Some empty hostnames",
|
desc: "Some empty hostnames",
|
||||||
hostnames: []v1alpha2.Hostname{"foo", "", "bar"},
|
hostnames: []v1alpha2.Hostname{"foo", "", "bar"},
|
||||||
expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`))",
|
expectedRule: "HostSNI(`foo`) || HostSNI(`bar`)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Valid hostname",
|
desc: "Valid hostname",
|
||||||
|
@ -4798,12 +4803,17 @@ func Test_hostSNIRule(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Multiple valid hostnames",
|
desc: "Multiple valid hostnames",
|
||||||
hostnames: []v1alpha2.Hostname{"foo", "bar"},
|
hostnames: []v1alpha2.Hostname{"foo", "bar"},
|
||||||
expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`))",
|
expectedRule: "HostSNI(`foo`) || HostSNI(`bar`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Multiple valid hostnames with wildcard",
|
||||||
|
hostnames: []v1alpha2.Hostname{"bar.foo", "foo.foo", "*.foo"},
|
||||||
|
expectedRule: "HostSNI(`bar.foo`) || HostSNI(`foo.foo`) || HostSNIRegexp(`^[a-zA-Z0-9-]+\\.foo$`)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Multiple overlapping hostnames",
|
desc: "Multiple overlapping hostnames",
|
||||||
hostnames: []v1alpha2.Hostname{"foo", "bar", "foo", "baz"},
|
hostnames: []v1alpha2.Hostname{"foo", "bar", "foo", "baz"},
|
||||||
expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`) || HostSNI(`baz`))",
|
expectedRule: "HostSNI(`foo`) || HostSNI(`bar`) || HostSNI(`baz`)",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue