Skip to content

Commit

Permalink
fix(gateway): check hostname intersection between HTTPRoute and Gatew…
Browse files Browse the repository at this point in the history
…ay listener (#4537)

Signed-off-by: Mike Beaumont <mjboamail@gmail.com>
  • Loading branch information
michaelbeaumont committed Jul 1, 2022
1 parent c1ad8ab commit 872bb5f
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package attachment

import (
"context"
"strings"

"github.com/pkg/errors"
kube_core "k8s.io/api/core/v1"
Expand Down Expand Up @@ -144,11 +145,35 @@ func getParentRefGateway(
return gateway, nil
}

func hostnamesIntersect(routeHostnames []gatewayapi.Hostname, listenerHostnames []gatewayapi.Hostname) bool {
if len(routeHostnames) == 0 {
return true
}

for _, routeHostname := range routeHostnames {
for _, listenerHostname := range listenerHostnames {
if listenerHostname == "" {
return true
}
if routeHostname == listenerHostname {
return true
}
// If the gateway hostname is a wildcard and the route hostname
// matches the wildcard
if suffix := strings.TrimPrefix(string(listenerHostname), "*."); len(suffix) != len(listenerHostname) {
return strings.HasSuffix(string(routeHostname), suffix)
}
}
}
return false
}

// EvaluateParentRefAttachment reports whether a route in the given namespace can attach
// via the given ParentRef.
func EvaluateParentRefAttachment(
ctx context.Context,
client kube_client.Client,
routeHostnames []gatewayapi.Hostname,
routeNs *kube_core.Namespace,
ref gatewayapi.ParentReference,
) (Attachment, error) {
Expand All @@ -160,5 +185,21 @@ func EvaluateParentRefAttachment(
return Unknown, nil
}

var listenerHostnames []gatewayapi.Hostname
for _, listener := range gateway.Spec.Listeners {
if ref.SectionName != nil && *ref.SectionName != listener.Name {
continue
}
listenerHostname := gatewayapi.Hostname("")
if listener.Hostname != nil {
listenerHostname = *listener.Hostname
}
listenerHostnames = append(listenerHostnames, listenerHostname)
}

if !hostnamesIntersect(routeHostnames, listenerHostnames) {
return Invalid, nil
}

return findRouteListenerAttachment(gateway, routeNs, ref.SectionName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import (
"github.com/kumahq/kuma/pkg/test"
)

func TestAllowedRoutes(t *testing.T) {
test.RunSpecs(t, "Gateway API AllowedRoutes support")
func TestRouteAttachment(t *testing.T) {
test.RunSpecs(t, "Gateway API route attachment support")
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,94 @@ var _ = BeforeSuite(func() {
Expect(err).NotTo(HaveOccurred())
})

var _ = Describe("Hostname intersection support", func() {
var kubeClient kube_client.Client
var simpleRef gatewayapi.ParentReference
BeforeEach(func() {
kubeClient = kube_client_fake.NewClientBuilder().WithScheme(k8sScheme).WithObjects(
gatewayClass,
gateway,
gatewayMultipleListeners,
defaultRouteNs,
otherRouteNs,
).Build()
})

checkAttachment := func(expected attachment.Attachment) func([]gatewayapi.Hostname) {
return func(routeHostnames []gatewayapi.Hostname) {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
routeHostnames,
defaultRouteNs,
simpleRef,
)
Expect(err).ToNot(HaveOccurred())
Expect(res).To(Equal(expected))
}
}

Context("all listeners", func() {
BeforeEach(func() {
simpleRef = *gatewayRef.DeepCopy()
simpleRef.Name = gatewayapi.ObjectName(gatewayMultipleListeners.Name)
})

It("matches with all listeners", func() {
checkAttachment(attachment.Allowed)([]gatewayapi.Hostname{"other.local"})
})
})

Context("listener without hostname", func() {
BeforeEach(func() {
simpleRef = *gatewayRef.DeepCopy()
simpleRef.Name = gatewayapi.ObjectName(gatewayMultipleListeners.Name)
simpleRef.SectionName = &anyHostnameListenerName
})

DescribeTable("matches", checkAttachment(attachment.Allowed),
Entry("without route hostname", nil),
Entry("with some hostname", []gatewayapi.Hostname{"other.local"}),
)
})

Context("listener with simple hostname", func() {
BeforeEach(func() {
simpleRef = *gatewayRef.DeepCopy()
simpleRef.Name = gatewayapi.ObjectName(gatewayMultipleListeners.Name)
simpleRef.SectionName = &simpleHostnameListenerName
})

DescribeTable("matches", checkAttachment(attachment.Allowed),
Entry("on exact match", []gatewayapi.Hostname{"simple.local"}),
Entry("if one matches", []gatewayapi.Hostname{"other.local", "simple.local"}),
)

DescribeTable("doesn't match", checkAttachment(attachment.Invalid),
Entry("without intersection", []gatewayapi.Hostname{"other.local"}),
)
})

Context("listener with wildcard hostname", func() {
BeforeEach(func() {
simpleRef = *gatewayRef.DeepCopy()
simpleRef.Name = gatewayapi.ObjectName(gatewayMultipleListeners.Name)
simpleRef.SectionName = &wildcardListenerName
})

DescribeTable("matches", checkAttachment(attachment.Allowed),
Entry("with a complete hostname", []gatewayapi.Hostname{"something.wildcard.local"}),
Entry("with a complete hostname with extra subdomains", []gatewayapi.Hostname{"something.else.wildcard.local"}),
Entry("with a complete hostname with extra subdomain and wildcard", []gatewayapi.Hostname{"*.else.wildcard.local"}),
Entry("with a wildcard hostname", []gatewayapi.Hostname{"*.wildcard.local"}),
)

DescribeTable("doesn't match", checkAttachment(attachment.Invalid),
Entry("without intersection", []gatewayapi.Hostname{"other.local"}),
)
})
})

var _ = Describe("AllowedRoutes support", func() {
var kubeClient kube_client.Client
BeforeEach(func() {
Expand All @@ -49,6 +137,7 @@ var _ = Describe("AllowedRoutes support", func() {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
nil,
defaultRouteNs,
simpleRef,
)
Expand All @@ -60,6 +149,7 @@ var _ = Describe("AllowedRoutes support", func() {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
nil,
defaultRouteNs,
simpleRef,
)
Expand All @@ -74,6 +164,7 @@ var _ = Describe("AllowedRoutes support", func() {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
nil,
otherRouteNs,
simpleRef,
)
Expand All @@ -88,6 +179,7 @@ var _ = Describe("AllowedRoutes support", func() {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
nil,
otherRouteNs,
simpleRef,
)
Expand All @@ -108,6 +200,7 @@ var _ = Describe("AllowedRoutes support", func() {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
nil,
defaultRouteNs,
parentRef,
)
Expand All @@ -122,6 +215,7 @@ var _ = Describe("AllowedRoutes support", func() {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
nil,
otherRouteNs,
parentRef,
)
Expand All @@ -135,6 +229,7 @@ var _ = Describe("AllowedRoutes support", func() {
res, err := attachment.EvaluateParentRefAttachment(
context.Background(),
kubeClient,
nil,
otherRouteNs,
parentRef,
)
Expand All @@ -145,12 +240,14 @@ var _ = Describe("AllowedRoutes support", func() {
})

var (
defaultNs = "default"
otherNs = "other"
fromAll = gatewayapi.NamespacesFromAll
fromSame = gatewayapi.NamespacesFromSame
gatewayGroup = gatewayapi.Group(gatewayapi.GroupName)
gatewayKind = gatewayapi.Kind("Gateway")
defaultNs = "default"
otherNs = "other"
fromAll = gatewayapi.NamespacesFromAll
fromSame = gatewayapi.NamespacesFromSame
gatewayGroup = gatewayapi.Group(gatewayapi.GroupName)
gatewayKind = gatewayapi.Kind("Gateway")
simpleHostname = gatewayapi.Hostname("simple.local")
anyTestHostname = gatewayapi.Hostname("*.wildcard.local")

listenerReady = []kube_meta.Condition{
{
Expand All @@ -159,8 +256,9 @@ var (
},
}

simpleListenerName = gatewayapi.SectionName("simple")
simpleListener = gatewayapi.Listener{
simpleListenerName = gatewayapi.SectionName("simple")
anyHostnameListenerName = simpleListenerName
simpleListener = gatewayapi.Listener{
Name: simpleListenerName,
Port: gatewayapi.PortNumber(80),
Protocol: gatewayapi.HTTPProtocolType,
Expand All @@ -170,11 +268,25 @@ var (
},
},
}
allNsListenerName = gatewayapi.SectionName("allNS")
allNsListener = gatewayapi.Listener{
wildcardListenerName = gatewayapi.SectionName("wildcard")
wildcardListener = gatewayapi.Listener{
Name: wildcardListenerName,
Port: gatewayapi.PortNumber(80),
Protocol: gatewayapi.HTTPProtocolType,
Hostname: &anyTestHostname,
AllowedRoutes: &gatewayapi.AllowedRoutes{
Namespaces: &gatewayapi.RouteNamespaces{
From: &fromSame,
},
},
}
allNsListenerName = gatewayapi.SectionName("allNS")
simpleHostnameListenerName = allNsListenerName
allNsListener = gatewayapi.Listener{
Name: allNsListenerName,
Port: gatewayapi.PortNumber(80),
Protocol: gatewayapi.HTTPProtocolType,
Hostname: &simpleHostname,
AllowedRoutes: &gatewayapi.AllowedRoutes{
Namespaces: &gatewayapi.RouteNamespaces{
From: &fromAll,
Expand Down Expand Up @@ -223,6 +335,7 @@ var (
GatewayClassName: "kuma",
Listeners: []gatewayapi.Listener{
simpleListener,
wildcardListener,
allNsListener,
},
},
Expand All @@ -232,6 +345,10 @@ var (
Name: simpleListenerName,
Conditions: listenerReady,
},
{
Name: wildcardListenerName,
Conditions: listenerReady,
},
{
Name: allNsListenerName,
Conditions: listenerReady,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (r *HTTPRouteReconciler) gapiToKumaRoutes(

// Convert GAPI parent refs into selectors
for i, ref := range route.Spec.ParentRefs {
refAttachment, err := attachment.EvaluateParentRefAttachment(ctx, r.Client, &routeNs, ref)
refAttachment, err := attachment.EvaluateParentRefAttachment(ctx, r.Client, route.Spec.Hostnames, &routeNs, ref)
if err != nil {
return nil, nil, errors.Wrapf(err, "unable to check parent ref %d", i)
}
Expand Down

0 comments on commit 872bb5f

Please sign in to comment.