Skip to content

Commit

Permalink
fix(MeshGateway): prevent duplicate virtual hosts (#10866)
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Beaumont <[email protected]>
  • Loading branch information
michaelbeaumont authored Jul 11, 2024
1 parent faedd08 commit 597067e
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 26 deletions.
14 changes: 4 additions & 10 deletions pkg/plugins/policies/core/xds/meshroute/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,22 +135,16 @@ func AddToListenerByHostname(
listenerTls *mesh_proto.MeshGateway_TLS_Conf,
hostInfos ...plugin_gateway.GatewayHostInfo,
) {
// This is the key by which we partition route rules, which is only
// necessary/possible when using HTTPS + listener hostnames
listenerKey := "*"
switch listenerProtocol {
case mesh_proto.MeshGateway_Listener_HTTPS, mesh_proto.MeshGateway_Listener_TLS:
listenerKey = listenerHostname
}
listenerEntry, ok := acc[listenerKey]
listenerEntry, ok := acc[listenerHostname]
if !ok {
listenerEntry = plugin_gateway.GatewayListenerHostname{
Hostname: listenerKey,
Hostname: listenerHostname,
Protocol: listenerProtocol,
TLS: listenerTls,
}
}
listenerEntry.HostInfos = append(listenerEntry.HostInfos, hostInfos...)
acc[listenerKey] = listenerEntry
acc[listenerHostname] = listenerEntry
}

func SortByHostname(listenersByHostname map[string]plugin_gateway.GatewayListenerHostname) []plugin_gateway.GatewayListenerHostname {
Expand Down
30 changes: 25 additions & 5 deletions pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,36 @@ func generateGatewayRoutes(
return nil, nil
}

resources := core_xds.NewResourceSet()
// Make a pass over the generators for each virtual host.
for _, hostInfos := range listenerHostnames {
routeConfigs := map[string]*envoy_routes.RouteConfigurationBuilder{}
switch info.Listener.Protocol {
case mesh_proto.MeshGateway_Listener_HTTPS:
for _, hostInfos := range listenerHostnames {
routeConfig := plugin_gateway.GenerateRouteConfig(
info.Proxy,
info.Listener.Protocol,
hostInfos.EnvoyRouteName(info.Listener.EnvoyListenerName),
)
routeConfigs[hostInfos.Hostname] = routeConfig
}
case mesh_proto.MeshGateway_Listener_HTTP:
routeConfig := plugin_gateway.GenerateRouteConfig(
info.Proxy,
info.Listener.Protocol,
hostInfos.EnvoyRouteName(info.Listener.EnvoyListenerName),
info.Listener.EnvoyListenerName+":*",
)
for _, hostInfos := range listenerHostnames {
routeConfigs[hostInfos.Hostname] = routeConfig
}
default:
return nil, nil
}

resources := core_xds.NewResourceSet()
// Make a pass over the generators for each virtual host.
for _, hostInfos := range listenerHostnames {
routeConfig := routeConfigs[hostInfos.Hostname]
for _, hostInfo := range hostInfos.HostInfos {
vh, err := plugin_gateway.GenerateVirtualHost(ctx, info, hostInfo.Host, hostInfo.Entries())
vh, err := plugin_gateway.GenerateVirtualHost(ctx, info, hostInfo.Host.Hostname, hostInfo.Entries())
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"slices"
"strings"

"golang.org/x/exp/maps"
"k8s.io/apimachinery/pkg/util/intstr"

common_api "github.com/kumahq/kuma/api/common/v1alpha1"
Expand Down Expand Up @@ -36,7 +37,20 @@ func sortRulesToHosts(
) []plugin_gateway.GatewayListenerHostname {
hostInfosByHostname := map[string]plugin_gateway.GatewayListenerHostname{}

for _, hostnameTag := range sublisteners {
// Iterate over the listeners in order
sublistenersByHostname := map[string]meshroute.Sublistener{}
for _, sublistener := range sublisteners {
sublistenersByHostname[sublistener.Hostname] = sublistener
}

// Keep track of which hostnames we've seen at the listener level
// so we can track which matches can't be matched at a different listener
// Very important for the HTTP listeners where every virtual host ends up
// under the same route config
var observedHostnames []string

for _, hostname := range match.SortHostnamesByExactnessDec(maps.Keys(sublistenersByHostname)) {
hostnameTag := sublistenersByHostname[hostname]
inboundListener := rules.NewInboundListenerHostname(
address,
port,
Expand Down Expand Up @@ -104,6 +118,15 @@ func sortRulesToHosts(
}
rules = append(rules, hostnameRules...)
}
// As mentioned above we shouldn't add rules if this hostname match
// can't match because of a listener hostname
var hostnameMatchUnmatchable bool
for _, observedHostname := range observedHostnames {
hostnameMatchUnmatchable = hostnameMatchUnmatchable || match.Contains(observedHostname, hostnameMatch)
}
if hostnameMatchUnmatchable {
continue
}
// Create an info for every hostname match
// We may end up duplicating info more than once so we copy it here
host := plugin_gateway.GatewayHost{
Expand Down Expand Up @@ -131,6 +154,7 @@ func sortRulesToHosts(
hostInfo,
)
}
observedHostnames = append(observedHostnames, hostname)
}

return meshroute.SortByHostname(hostInfosByHostname)
Expand Down
6 changes: 1 addition & 5 deletions pkg/plugins/runtime/gateway/filter_chain_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,7 @@ func (g *HTTPFilterChainGenerator) Generate(
// if there's already a filter chain, we have nothing to do.
chain := newHTTPFilterChain(ctx.Mesh, info)

if len(info.ListenerHostnames) != 1 {
return nil, nil, errors.New("expected exactly one ListenerHostname with HTTP listener")
}
routeName := info.ListenerHostnames[0].EnvoyRouteName(info.Listener.EnvoyListenerName)
chain.Configure(envoy_listeners.HttpDynamicRoute(routeName))
chain.Configure(envoy_listeners.HttpDynamicRoute(info.Listener.EnvoyListenerName + ":*"))
return nil, []*envoy_listeners.FilterChainBuilder{chain}, nil
}

Expand Down
11 changes: 9 additions & 2 deletions pkg/plugins/runtime/gateway/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,18 @@ type GatewayHost struct {

type GatewayListenerHostname struct {
Hostname string
Protocol mesh_proto.MeshGateway_Listener_Protocol
TLS *mesh_proto.MeshGateway_TLS_Conf
HostInfos []GatewayHostInfo
}

func (h GatewayListenerHostname) EnvoyRouteName(envoyListenerName string) string {
return envoyListenerName + ":" + h.Hostname
switch h.Protocol {
case mesh_proto.MeshGateway_Listener_TCP, mesh_proto.MeshGateway_Listener_HTTP:
return envoyListenerName + ":*"
default:
return envoyListenerName + ":" + h.Hostname
}
}

type GatewayListener struct {
Expand Down Expand Up @@ -340,7 +346,7 @@ func (g Generator) generateRDS(ctx xds_context.Context, info GatewayListenerInfo

// Make a pass over the generators for each virtual host.
for _, hostInfo := range hostInfos.HostInfos {
vh, err := GenerateVirtualHost(ctx, info, hostInfo.Host, hostInfo.Entries())
vh, err := GenerateVirtualHost(ctx, info, hostInfo.Host.Hostname, hostInfo.Entries())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -450,6 +456,7 @@ func MakeGatewayListener(

listenerHostnames = append(listenerHostnames, GatewayListenerHostname{
Hostname: hostname,
Protocol: listeners[0].GetProtocol(),
TLS: hostAcc.tls,
HostInfos: hostInfos,
})
Expand Down
6 changes: 3 additions & 3 deletions pkg/plugins/runtime/gateway/route_table_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ const emptyGatewayMsg = "This is a Kuma MeshGateway. No routes match this MeshGa

// GenerateVirtualHost generates xDS resources for the current route table.
func GenerateVirtualHost(
ctx xds_context.Context, info GatewayListenerInfo, host GatewayHost, routes []route.Entry,
ctx xds_context.Context, info GatewayListenerInfo, hostname string, routes []route.Entry,
) (
*envoy_virtual_hosts.VirtualHostBuilder, error,
) {
vh := envoy_virtual_hosts.NewVirtualHostBuilder(info.Proxy.APIVersion, host.Hostname).Configure(
envoy_virtual_hosts.DomainNames(host.Hostname),
vh := envoy_virtual_hosts.NewVirtualHostBuilder(info.Proxy.APIVersion, hostname).Configure(
envoy_virtual_hosts.DomainNames(hostname),
)

// Ensure that we get TLS on HTTPS protocol listeners or crossMesh.
Expand Down
1 change: 1 addition & 0 deletions test/e2e_env/gatewayapi/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func TestConformance(t *testing.T) {
ManifestFS: []fs.FS{&conformance.Manifests},
SupportedFeatures: sets.New(
features.SupportGateway,
features.SupportGatewayHTTPListenerIsolation,
features.SupportGatewayPort8080,
features.SupportReferenceGrant,
features.SupportHTTPRouteResponseHeaderModification,
Expand Down

0 comments on commit 597067e

Please sign in to comment.