diff --git a/pkg/plugins/policies/core/xds/meshroute/gateway.go b/pkg/plugins/policies/core/xds/meshroute/gateway.go index 12913b495d4b..dadce9a742b7 100644 --- a/pkg/plugins/policies/core/xds/meshroute/gateway.go +++ b/pkg/plugins/policies/core/xds/meshroute/gateway.go @@ -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 { diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway.go b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway.go index f5cd75978c90..3269c2d00b3d 100644 --- a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway.go +++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway.go @@ -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 } diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway_routes.go b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway_routes.go index 056648350284..b9d51d803d2c 100644 --- a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway_routes.go +++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/gateway_routes.go @@ -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" @@ -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, @@ -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{ @@ -131,6 +154,7 @@ func sortRulesToHosts( hostInfo, ) } + observedHostnames = append(observedHostnames, hostname) } return meshroute.SortByHostname(hostInfosByHostname) diff --git a/pkg/plugins/runtime/gateway/filter_chain_generator.go b/pkg/plugins/runtime/gateway/filter_chain_generator.go index a8526f3e5490..870edb213b5d 100644 --- a/pkg/plugins/runtime/gateway/filter_chain_generator.go +++ b/pkg/plugins/runtime/gateway/filter_chain_generator.go @@ -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 } diff --git a/pkg/plugins/runtime/gateway/generator.go b/pkg/plugins/runtime/gateway/generator.go index 6c2a88a11eb4..0b0cca74102e 100644 --- a/pkg/plugins/runtime/gateway/generator.go +++ b/pkg/plugins/runtime/gateway/generator.go @@ -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 { @@ -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 } @@ -450,6 +456,7 @@ func MakeGatewayListener( listenerHostnames = append(listenerHostnames, GatewayListenerHostname{ Hostname: hostname, + Protocol: listeners[0].GetProtocol(), TLS: hostAcc.tls, HostInfos: hostInfos, }) diff --git a/pkg/plugins/runtime/gateway/route_table_generator.go b/pkg/plugins/runtime/gateway/route_table_generator.go index dfdae61ac9ec..b0e198b1c01d 100644 --- a/pkg/plugins/runtime/gateway/route_table_generator.go +++ b/pkg/plugins/runtime/gateway/route_table_generator.go @@ -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. diff --git a/test/e2e_env/gatewayapi/conformance_test.go b/test/e2e_env/gatewayapi/conformance_test.go index 1c2ce7135c59..68d68c7093d8 100644 --- a/test/e2e_env/gatewayapi/conformance_test.go +++ b/test/e2e_env/gatewayapi/conformance_test.go @@ -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,