Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport of Fix hostname alignment checks for HTTPRoutes into release/1.15.x #16315

Merged
Merged
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
13 changes: 7 additions & 6 deletions agent/consul/discoverychain/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type GatewayChainSynthesizer struct {
trustDomain string
suffix string
gateway *structs.APIGatewayConfigEntry
hostname string
matchesByHostname map[string][]hostnameMatch
tcpRoutes []structs.TCPRouteConfigEntry
}
Expand Down Expand Up @@ -44,17 +45,17 @@ func (l *GatewayChainSynthesizer) AddTCPRoute(route structs.TCPRouteConfigEntry)
l.tcpRoutes = append(l.tcpRoutes, route)
}

// SetHostname sets the base hostname for a listener that this is being synthesized for
func (l *GatewayChainSynthesizer) SetHostname(hostname string) {
l.hostname = hostname
}

// AddHTTPRoute takes a new route and flattens its rule matches out per hostname.
// This is required since a single route can specify multiple hostnames, and a
// single hostname can be specified in multiple routes. Routing for a given
// hostname must behave based on the aggregate of all rules that apply to it.
func (l *GatewayChainSynthesizer) AddHTTPRoute(route structs.HTTPRouteConfigEntry) {
hostnames := route.Hostnames
if len(route.Hostnames) == 0 {
// add a wildcard if there are no explicit hostnames set
hostnames = append(hostnames, "*")
}

hostnames := route.FilteredHostnames(l.hostname)
for _, host := range hostnames {
matches, ok := l.matchesByHostname[host]
if !ok {
Expand Down
3 changes: 3 additions & 0 deletions agent/consul/discoverychain/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ func TestGatewayChainSynthesizer_AddHTTPRoute(t *testing.T) {

gatewayChainSynthesizer := NewGatewayChainSynthesizer(datacenter, "domain", "suffix", gateway)

gatewayChainSynthesizer.SetHostname("*")
gatewayChainSynthesizer.AddHTTPRoute(tc.route)

require.Equal(t, tc.expectedMatchesByHostname, gatewayChainSynthesizer.matchesByHostname)
Expand Down Expand Up @@ -621,6 +622,8 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) {

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
tc.synthesizer.SetHostname("*")

for _, tcpRoute := range tc.tcpRoutes {
tc.synthesizer.AddTCPRoute(*tcpRoute)
}
Expand Down
8 changes: 8 additions & 0 deletions agent/consul/gateways/controller_gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,14 @@ func (g *gatewayMeta) bindRoute(listener *structs.APIGatewayListener, bound *str
return false, nil
}

if route, ok := route.(*structs.HTTPRouteConfigEntry); ok {
// check our hostnames
hostnames := route.FilteredHostnames(listener.GetHostname())
if len(hostnames) == 0 {
return false, fmt.Errorf("failed to bind route to gateway %s: listener %s is does not have any hostnames that match the route", route.GetName(), g.Gateway.Name)
}
}

if listener.Protocol == route.GetProtocol() && bound.BindRoute(structs.ResourceReference{
Kind: route.GetKind(),
Name: route.GetName(),
Expand Down
15 changes: 8 additions & 7 deletions agent/proxycfg/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI
}

// Create a synthesized discovery chain for each service.
services, upstreams, compiled, err := c.synthesizeChains(datacenter, listener.Protocol, listener.Port, listener.Name, boundListener)
services, upstreams, compiled, err := c.synthesizeChains(datacenter, listener, boundListener)
if err != nil {
return configSnapshotIngressGateway{}, err
}
Expand Down Expand Up @@ -836,7 +836,7 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI
}, nil
}

func (c *configSnapshotAPIGateway) synthesizeChains(datacenter string, protocol structs.APIGatewayListenerProtocol, port int, name string, boundListener structs.BoundAPIGatewayListener) ([]structs.IngressService, structs.Upstreams, []*structs.CompiledDiscoveryChain, error) {
func (c *configSnapshotAPIGateway) synthesizeChains(datacenter string, listener structs.APIGatewayListener, boundListener structs.BoundAPIGatewayListener) ([]structs.IngressService, structs.Upstreams, []*structs.CompiledDiscoveryChain, error) {
chains := []*structs.CompiledDiscoveryChain{}
trustDomain := ""

Expand All @@ -852,12 +852,13 @@ DOMAIN_LOOP:
}
}

synthesizer := discoverychain.NewGatewayChainSynthesizer(datacenter, trustDomain, name, c.GatewayConfig)
synthesizer := discoverychain.NewGatewayChainSynthesizer(datacenter, trustDomain, listener.Name, c.GatewayConfig)
synthesizer.SetHostname(listener.GetHostname())
for _, routeRef := range boundListener.Routes {
switch routeRef.Kind {
case structs.HTTPRoute:
route, ok := c.HTTPRoutes.Get(routeRef)
if !ok || protocol != structs.ListenerProtocolHTTP {
if !ok || listener.Protocol != structs.ListenerProtocolHTTP {
continue
}
synthesizer.AddHTTPRoute(*route)
Expand All @@ -869,7 +870,7 @@ DOMAIN_LOOP:
}
case structs.TCPRoute:
route, ok := c.TCPRoutes.Get(routeRef)
if !ok || protocol != structs.ListenerProtocolTCP {
if !ok || listener.Protocol != structs.ListenerProtocolTCP {
continue
}
synthesizer.AddTCPRoute(*route)
Expand Down Expand Up @@ -901,9 +902,9 @@ DOMAIN_LOOP:
DestinationNamespace: service.NamespaceOrDefault(),
DestinationPartition: service.PartitionOrDefault(),
IngressHosts: service.Hosts,
LocalBindPort: port,
LocalBindPort: listener.Port,
Config: map[string]interface{}{
"protocol": string(protocol),
"protocol": string(listener.Protocol),
},
})
}
Expand Down
7 changes: 7 additions & 0 deletions agent/structs/config_entry_gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,13 @@ type APIGatewayListener struct {
TLS APIGatewayTLSConfiguration
}

func (l APIGatewayListener) GetHostname() string {
if l.Hostname != "" {
return l.Hostname
}
return "*"
}

// APIGatewayTLSConfiguration specifies the configuration of a listener’s
// TLS settings.
type APIGatewayTLSConfiguration struct {
Expand Down
26 changes: 26 additions & 0 deletions agent/structs/config_entry_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package structs

import (
"fmt"
"strings"

"github.com/hashicorp/consul/acl"
)
Expand Down Expand Up @@ -121,6 +122,31 @@ func (e *HTTPRouteConfigEntry) GetRaftIndex() *RaftIndex {
return &e.RaftIndex
}

func (e *HTTPRouteConfigEntry) FilteredHostnames(listenerHostname string) []string {
if len(e.Hostnames) == 0 {
// we have no hostnames specified here, so treat it like a wildcard
return []string{listenerHostname}
}

wildcardHostname := strings.ContainsRune(listenerHostname, '*') || listenerHostname == "*"
listenerHostname = strings.TrimPrefix(strings.TrimPrefix(listenerHostname, "*"), ".")

hostnames := []string{}
for _, hostname := range e.Hostnames {
if wildcardHostname {
if strings.HasSuffix(hostname, listenerHostname) {
hostnames = append(hostnames, hostname)
}
continue
}

if hostname == listenerHostname {
hostnames = append(hostnames, hostname)
}
}
return hostnames
}

// HTTPMatch specifies the criteria that should be
// used in determining whether or not a request should
// be routed to a given set of services.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

snapshot_envoy_admin localhost:20000 api-gateway primary || true
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services {
name = "api-gateway"
kind = "api-gateway"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/bin/bash

set -euo pipefail

upsert_config_entry primary '
kind = "api-gateway"
name = "api-gateway"
listeners = [
{
name = "listener-one"
port = 9999
protocol = "http"
hostname = "*.consul.example"
},
{
name = "listener-two"
port = 9998
protocol = "http"
hostname = "foo.bar.baz"
},
{
name = "listener-three"
port = 9997
protocol = "http"
hostname = "*.consul.example"
},
{
name = "listener-four"
port = 9996
protocol = "http"
hostname = "*.consul.example"
},
{
name = "listener-five"
port = 9995
protocol = "http"
hostname = "foo.bar.baz"
}
]
'

upsert_config_entry primary '
Kind = "proxy-defaults"
Name = "global"
Config {
protocol = "http"
}
'

upsert_config_entry primary '
kind = "http-route"
name = "api-gateway-route-one"
hostnames = ["test.consul.example"]
rules = [
{
services = [
{
name = "s1"
}
]
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-one"
},
]
'

upsert_config_entry primary '
kind = "http-route"
name = "api-gateway-route-two"
hostnames = ["foo.bar.baz"]
rules = [
{
services = [
{
name = "s1"
}
]
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-two"
},
]
'

upsert_config_entry primary '
kind = "http-route"
name = "api-gateway-route-three"
hostnames = ["foo.bar.baz"]
rules = [
{
services = [
{
name = "s1"
}
]
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-three"
},
]
'

upsert_config_entry primary '
kind = "http-route"
name = "api-gateway-route-four"
rules = [
{
services = [
{
name = "s1"
}
]
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-four"
},
]
'

upsert_config_entry primary '
kind = "http-route"
name = "api-gateway-route-five"
rules = [
{
services = [
{
name = "s1"
}
]
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-five"
},
]
'

register_services primary

gen_envoy_bootstrap api-gateway 20000 primary true
gen_envoy_bootstrap s1 19000
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES api-gateway-primary"
Loading