Repository: kumahq/kuma
Pull request #3815 (2022-02-11 12:37:22)

fix(k8s): reconcile serviceMaps when using mesh namespace annotation
### Summary

We were not looking at annotations on the namespace when looking
at config map that need to be reconciled when service changes.

This would cause issue if pods were not individually annotated.

### Testing

- [ ] Unit tests
- [ ] E2E tests
- [ ] Manual testing on Universal
- [ ] Manual testing on Kubernetes

### Backwards compatibility

- ~~[ ] Update [`UPGRADE.md`](/UPGRADE.md) with any steps users will need to take when upgrading.~~
- ~~[ ] Add `backport-to-stable` label if the code follows our [backporting policy](/CONTRIBUTING.md#backporting)~~


codecov-commenter: # [Codecov](https://codecov.io/gh/kumahq/kuma/pull/3815?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq) Report
> Merging [#3815](https://codecov.io/gh/kumahq/kuma/pull/3815?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq) (ccd45f1) into [master](https://codecov.io/gh/kumahq/kuma/commit/44028ce90e5298fc8853c5d38280f07ae07442b6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq) (44028ce) will **decrease** coverage by `0.01%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/kumahq/kuma/pull/3815/graphs/tree.svg?width=650&height=150&src=pr&token=D7Yvqs6rwV&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq)](https://codecov.io/gh/kumahq/kuma/pull/3815?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq)

```diff
@@            Coverage Diff             @@
##           master    #3815      +/-   ##
==========================================
- Coverage   55.81%   55.79%   -0.02%     
==========================================
  Files         907      907              
  Lines       53750    53753       +3     
==========================================
- Hits        29999    29994       -5     
- Misses      21342    21348       +6     
- Partials     2409     2411       +2     
```


| [Impacted Files](https://codecov.io/gh/kumahq/kuma/pull/3815?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq) | Coverage Δ | |
|---|---|---|
| [...ns/runtime/k8s/controllers/configmap\_controller.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL3BsdWdpbnMvcnVudGltZS9rOHMvY29udHJvbGxlcnMvY29uZmlnbWFwX2NvbnRyb2xsZXIuZ28=) | `7.92% <0.00%> (-0.25%)` | :arrow_down: |
| [pkg/xds/cache/once/cache.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL3hkcy9jYWNoZS9vbmNlL2NhY2hlLmdv) | `82.05% <0.00%> (-5.13%)` | :arrow_down: |
| [pkg/plugins/runtime/gateway/route/sorter.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL3BsdWdpbnMvcnVudGltZS9nYXRld2F5L3JvdXRlL3NvcnRlci5nbw==) | `61.53% <0.00%> (-5.13%)` | :arrow_down: |
| [pkg/xds/cache/once/once.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL3hkcy9jYWNoZS9vbmNlL29uY2UuZ28=) | `95.00% <0.00%> (-5.00%)` | :arrow_down: |
| [pkg/plugins/leader/postgres/leader\_elector.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL3BsdWdpbnMvbGVhZGVyL3Bvc3RncmVzL2xlYWRlcl9lbGVjdG9yLmdv) | `97.87% <0.00%> (-2.13%)` | :arrow_down: |
| [pkg/dns/vips\_allocator.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL2Rucy92aXBzX2FsbG9jYXRvci5nbw==) | `72.34% <0.00%> (-1.42%)` | :arrow_down: |
| [pkg/xds/generator/direct\_access\_proxy\_generator.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL3hkcy9nZW5lcmF0b3IvZGlyZWN0X2FjY2Vzc19wcm94eV9nZW5lcmF0b3IuZ28=) | `88.76% <0.00%> (+1.12%)` | :arrow_up: |
| [...s/authn/api-server/tokens/admin\_token\_bootstrap.go](https://codecov.io/gh/kumahq/kuma/pull/3815/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq#diff-cGtnL3BsdWdpbnMvYXV0aG4vYXBpLXNlcnZlci90b2tlbnMvYWRtaW5fdG9rZW5fYm9vdHN0cmFwLmdv) | `82.00% <0.00%> (+4.00%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kumahq/kuma/pull/3815?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kumahq/kuma/pull/3815?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq). Last update [44028ce...ccd45f1](https://codecov.io/gh/kumahq/kuma/pull/3815?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kumahq).

lahabana: @mergifyio update
mergify[bot]: > update

#### ✅ Branch has been successfully updated



<!--
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "update", "conclusion": "success"}
-*- Mergify Payload End -*-
-->

lahabana: @mergifyio update
mergify[bot]: > update

#### ✅ Branch has been successfully updated



<!--
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "update", "conclusion": "success"}
-*- Mergify Payload End -*-
-->


Patch:
diff --git a/app/kuma-dp/cmd/run.go b/app/kuma-dp/cmd/run.go
index d7f48779a..8b95d2e42 100644
--- a/app/kuma-dp/cmd/run.go
+++ b/app/kuma-dp/cmd/run.go
@@ -132,7 +132,7 @@ func newRunCmd(opts kuma_cmd.RunCmdOpts, rootCtx *RootContext) *cobra.Command {
 
 			if cfg.DataplaneRuntime.TokenPath != "" {
 				if err := kumadp_config.ValidateTokenPath(cfg.DataplaneRuntime.TokenPath); err != nil {
-					return err
+					return errors.Wrapf(err, "dataplane token is invalid, in Kubernetes you must mount a serviceAccount token, in universal you must start your proxy with a generated token.")
 				}
 			}
 
diff --git a/go.mod b/go.mod
index 288afe426..8564eaa13 100644
--- a/go.mod
+++ b/go.mod
@@ -149,7 +149,6 @@ require (
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
 	github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
-	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/opencontainers/runc v1.0.2 // indirect
@@ -184,7 +183,6 @@ require (
 	google.golang.org/appengine v1.6.7 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.66.2 // indirect
-	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 	k8s.io/component-base v0.23.4 // indirect
diff --git a/pkg/plugins/runtime/gateway/cluster_generator.go b/pkg/plugins/runtime/gateway/cluster_generator.go
index c2730afd7..bae451c3f 100644
--- a/pkg/plugins/runtime/gateway/cluster_generator.go
+++ b/pkg/plugins/runtime/gateway/cluster_generator.go
@@ -7,7 +7,6 @@ import (
 	"github.com/pkg/errors"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
-	"github.com/kumahq/kuma/pkg/core/datasource"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 	"github.com/kumahq/kuma/pkg/plugins/runtime/gateway/match"
@@ -21,8 +20,7 @@ import (
 // ClusterGenerator generates Envoy clusters and their corresponding
 // load assignments for both mesh services and external services.
 type ClusterGenerator struct {
-	DataSourceLoader datasource.Loader
-	Zone             string
+	Zone string
 }
 
 // GenerateHost generates clusters for all the services targeted in the current route table.
@@ -62,7 +60,7 @@ func (c *ClusterGenerator) GenerateClusters(ctx xds_context.Context, info Gatewa
 				"service", service,
 			)
 
-			r, err = c.generateExternalCluster(ctx, info, matched, dest)
+			r, err = c.generateExternalCluster(ctx.Mesh, info, matched, dest)
 		} else {
 			log.Info("generating mesh cluster resource",
 				"service", service,
@@ -143,7 +141,7 @@ func (c *ClusterGenerator) generateMeshCluster(
 }
 
 func (c *ClusterGenerator) generateExternalCluster(
-	ctx xds_context.Context,
+	ctx xds_context.MeshContext,
 	info GatewayListenerInfo,
 	externalServices []*core_mesh.ExternalServiceResource,
 	dest *route.Destination,
@@ -151,7 +149,7 @@ func (c *ClusterGenerator) generateExternalCluster(
 	var endpoints []core_xds.Endpoint
 
 	for _, ext := range externalServices {
-		ep, err := topology.NewExternalServiceEndpoint(ext, ctx.Mesh.Resource, c.DataSourceLoader, c.Zone)
+		ep, err := topology.NewExternalServiceEndpoint(ext, ctx.Resource, ctx.DataSourceLoader, c.Zone)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/plugins/runtime/gateway/filter_chain_generator.go b/pkg/plugins/runtime/gateway/filter_chain_generator.go
index 7e6ad1d98..854643b62 100644
--- a/pkg/plugins/runtime/gateway/filter_chain_generator.go
+++ b/pkg/plugins/runtime/gateway/filter_chain_generator.go
@@ -18,7 +18,6 @@ import (
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
-	"github.com/kumahq/kuma/pkg/core/datasource"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	"github.com/kumahq/kuma/pkg/core/validators"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
@@ -62,7 +61,7 @@ type HTTPFilterChainGenerator struct {
 }
 
 func (g *HTTPFilterChainGenerator) Generate(
-	ctx xds_context.Context, info GatewayListenerInfo, _ []GatewayHost,
+	ctx xds_context.MeshContext, info GatewayListenerInfo, _ []GatewayHost,
 ) (
 	*core_xds.ResourceSet, []*envoy_listeners.FilterChainBuilder, error,
 ) {
@@ -75,11 +74,10 @@ func (g *HTTPFilterChainGenerator) Generate(
 
 // HTTPSFilterChainGenerator generates a filter chain for an HTTPS listener.
 type HTTPSFilterChainGenerator struct {
-	DataSourceLoader datasource.Loader
 }
 
 func (g *HTTPSFilterChainGenerator) Generate(
-	ctx xds_context.Context, info GatewayListenerInfo, hosts []GatewayHost,
+	ctx xds_context.MeshContext, info GatewayListenerInfo, hosts []GatewayHost,
 ) (
 	*core_xds.ResourceSet, []*envoy_listeners.FilterChainBuilder, error,
 ) {
@@ -161,11 +159,11 @@ func (g *HTTPSFilterChainGenerator) Generate(
 }
 
 func (g *HTTPSFilterChainGenerator) generateCertificateSecret(
-	ctx xds_context.Context,
+	ctx xds_context.MeshContext,
 	host GatewayHost,
 	secret *system_proto.DataSource,
 ) (*envoy_extensions_transport_sockets_tls_v3.Secret, error) {
-	data, err := g.DataSourceLoader.Load(context.Background(), ctx.Mesh.Resource.GetMeta().GetName(), secret)
+	data, err := ctx.DataSourceLoader.Load(context.Background(), ctx.Resource.GetMeta().GetName(), secret)
 	if err != nil {
 		return nil, err
 	}
@@ -218,7 +216,7 @@ func newDownstreamTypedConfig() *envoy_extensions_transport_sockets_tls_v3.Downs
 	return conf
 }
 
-func newFilterChain(ctx xds_context.Context, info GatewayListenerInfo) *envoy_listeners.FilterChainBuilder {
+func newFilterChain(ctx xds_context.MeshContext, info GatewayListenerInfo) *envoy_listeners.FilterChainBuilder {
 	// A Gateway is a single service across all listeners.
 	service := info.Proxy.Dataplane.Spec.GetIdentifyingService()
 
@@ -263,7 +261,7 @@ func newFilterChain(ctx xds_context.Context, info GatewayListenerInfo) *envoy_li
 		// is a no-op unless we later add a per-route configuration.
 		envoy_listeners.RateLimit([]*core_mesh.RateLimitResource{nil}),
 		envoy_listeners.DefaultCompressorFilter(),
-		envoy_listeners.Tracing(ctx.Mesh.GetTracingBackend(info.Proxy.Policies.TrafficTrace), service),
+		envoy_listeners.Tracing(ctx.GetTracingBackend(info.Proxy.Policies.TrafficTrace), service),
 		// In mesh proxies, the access log is configured on the outbound
 		// listener, which is why we index the Logs slice by destination
 		// service name.  A Gateway listener by definition forwards traffic
@@ -272,11 +270,11 @@ func newFilterChain(ctx xds_context.Context, info GatewayListenerInfo) *envoy_li
 		// match the log policy for the generic pass through service. This
 		// will be the only policy available for a Dataplane with no outbounds.
 		envoy_listeners.HttpAccessLog(
-			ctx.Mesh.Resource.Meta.GetName(),
+			ctx.Resource.Meta.GetName(),
 			envoy.TrafficDirectionInbound,
 			service,                // Source service is the gateway service.
 			mesh_proto.MatchAllTag, // Destination service could be anywhere, depending on the routes.
-			ctx.Mesh.GetLoggingBackend(info.Proxy.Policies.TrafficLogs[core_mesh.PassThroughService]),
+			ctx.GetLoggingBackend(info.Proxy.Policies.TrafficLogs[core_mesh.PassThroughService]),
 			info.Proxy,
 		),
 	)
diff --git a/pkg/plugins/runtime/gateway/generator.go b/pkg/plugins/runtime/gateway/generator.go
index 5aa65f23f..0d73c2c63 100644
--- a/pkg/plugins/runtime/gateway/generator.go
+++ b/pkg/plugins/runtime/gateway/generator.go
@@ -3,11 +3,11 @@ package gateway
 import (
 	"fmt"
 	"sort"
+	"strings"
 
 	"github.com/pkg/errors"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
-	"github.com/kumahq/kuma/pkg/core/datasource"
 	"github.com/kumahq/kuma/pkg/core/permissions"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	"github.com/kumahq/kuma/pkg/core/resources/model"
@@ -75,7 +75,7 @@ type GatewayListenerInfo struct {
 // a specific protocol.
 // A FilterChainGenerator can be host-specific or shared amongst hosts.
 type FilterChainGenerator interface {
-	Generate(xds_context.Context, GatewayListenerInfo, []GatewayHost) (*core_xds.ResourceSet, []*envoy_listeners.FilterChainBuilder, error)
+	Generate(xds_context.MeshContext, GatewayListenerInfo, []GatewayHost) (*core_xds.ResourceSet, []*envoy_listeners.FilterChainBuilder, error)
 }
 
 // Generator generates xDS resources for an entire Gateway.
@@ -83,7 +83,6 @@ type Generator struct {
 	FilterChainGenerators filterChainGenerators
 	ClusterGenerator      ClusterGenerator
 	Zone                  string
-	DataSourceLoader      datasource.Loader
 }
 
 type filterChainGenerators struct {
@@ -99,7 +98,7 @@ func (g *filterChainGenerators) For(ctx xds_context.Context, info GatewayListene
 // Gateway and returns information about the listeners, routes and applied
 // policies.
 func GatewayListenerInfoFromProxy(
-	ctx xds_context.MeshContext, proxy *core_xds.Proxy, zone string, dataSourceLoader datasource.Loader,
+	ctx xds_context.MeshContext, proxy *core_xds.Proxy, zone string,
 ) (
 	[]GatewayListenerInfo, error,
 ) {
@@ -153,7 +152,8 @@ func GatewayListenerInfoFromProxy(
 		ctx.Resources.Dataplanes().Items,
 		ctx.Resources.ZoneIngresses().Items,
 		ctx.Resources.ZoneEgresses().Items,
-		matchedExternalServices, dataSourceLoader,
+		matchedExternalServices,
+		ctx.DataSourceLoader,
 	)
 
 	for port, listeners := range collapsed {
@@ -172,14 +172,6 @@ func GatewayListenerInfoFromProxy(
 			return nil, err
 		}
 
-		hosts = RedistributeWildcardRoutes(hosts)
-
-		// Sort by reverse hostname, so that fully qualified hostnames sort
-		// before wildcard domains, and "*" is last.
-		sort.Slice(hosts, func(i, j int) bool {
-			return hosts[i].Hostname > hosts[j].Hostname
-		})
-
 		var hostInfos []GatewayHostInfo
 		for _, host := range hosts {
 			log.V(1).Info("applying merged traffic routes",
@@ -209,7 +201,7 @@ func GatewayListenerInfoFromProxy(
 func (g Generator) Generate(ctx xds_context.Context, proxy *core_xds.Proxy) (*core_xds.ResourceSet, error) {
 	resources := core_xds.NewResourceSet()
 
-	listenerInfos, err := GatewayListenerInfoFromProxy(ctx.Mesh, proxy, g.Zone, g.DataSourceLoader)
+	listenerInfos, err := GatewayListenerInfoFromProxy(ctx.Mesh, proxy, g.Zone)
 	if err != nil {
 		return nil, errors.Wrap(err, "error generating listener info from Proxy")
 	}
@@ -220,7 +212,7 @@ func (g Generator) Generate(ctx xds_context.Context, proxy *core_xds.Proxy) (*co
 			return nil, errors.New("no support for protocol")
 		}
 
-		ldsResources, err := g.generateLDS(ctx, info, info.HostInfos)
+		ldsResources, err := g.generateLDS(ctx.Mesh, info, info.HostInfos)
 		if err != nil {
 			return nil, err
 		}
@@ -236,9 +228,9 @@ func (g Generator) Generate(ctx xds_context.Context, proxy *core_xds.Proxy) (*co
 	return resources, nil
 }
 
-func (g Generator) generateLDS(ctx xds_context.Context, info GatewayListenerInfo, hostInfos []GatewayHostInfo) (*core_xds.ResourceSet, error) {
+func (g Generator) generateLDS(ctx xds_context.MeshContext, info GatewayListenerInfo, hostInfos []GatewayHostInfo) (*core_xds.ResourceSet, error) {
 	resources := core_xds.NewResourceSet()
-	listenerBuilder := GenerateListener(ctx, info)
+	listenerBuilder := GenerateListener(info)
 
 	var gatewayHosts []GatewayHost
 	for _, hostInfo := range hostInfos {
@@ -355,6 +347,14 @@ func MakeGatewayListener(
 		hosts = append(hosts, host)
 	}
 
+	hosts = RedistributeWildcardRoutes(hosts)
+
+	// Sort by reverse hostname, so that fully qualified hostnames sort
+	// before wildcard domains, and "*" is last.
+	sort.Slice(hosts, func(i, j int) bool {
+		return hosts[i].Hostname > hosts[j].Hostname
+	})
+
 	return listener, hosts, nil
 }
 
@@ -403,6 +403,13 @@ func RedistributeWildcardRoutes(
 			continue
 		}
 
+		appendRoutesToHost := func(host GatewayHost) {
+			// Note that if we already have a virtualhost for this
+			// name, and add the route to it, it might be a duplicate.
+			host.Routes = append(host.Routes, r)
+			hostsByName[host.Hostname] = host
+		}
+
 		for _, n := range names {
 			host, ok := hostsByName[n]
 
@@ -412,14 +419,21 @@ func RedistributeWildcardRoutes(
 			if !ok {
 				host = wild
 				host.Routes = nil
+				host.Hostname = n
 			}
 
-			// Note that if we already have a virtualhost for this
-			// name, and add the route to it, it might be a duplicate.
-			host.Routes = append(host.Routes, r)
-			host.Hostname = n
-
-			hostsByName[n] = host
+			appendRoutesToHost(host)
+
+			if suffix := strings.TrimPrefix(n, "*."); len(suffix) != len(n) {
+				// An alternative to this might be precalculating
+				// a table of suffixes to hosts
+				for hostName, host := range hostsByName {
+					if !strings.HasSuffix(hostName, suffix) {
+						continue
+					}
+					appendRoutesToHost(host)
+				}
+			}
 		}
 	}
 
diff --git a/pkg/plugins/runtime/gateway/listener_generator.go b/pkg/plugins/runtime/gateway/listener_generator.go
index 6350f4b11..35624425f 100644
--- a/pkg/plugins/runtime/gateway/listener_generator.go
+++ b/pkg/plugins/runtime/gateway/listener_generator.go
@@ -3,7 +3,6 @@ package gateway
 import (
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
-	xds_context "github.com/kumahq/kuma/pkg/xds/context"
 	envoy_listeners "github.com/kumahq/kuma/pkg/xds/envoy/listeners"
 	envoy_names "github.com/kumahq/kuma/pkg/xds/envoy/names"
 )
@@ -27,7 +26,7 @@ func SupportsProtocol(p mesh_proto.MeshGateway_Listener_Protocol) bool {
 	}
 }
 
-func GenerateListener(ctx xds_context.Context, info GatewayListenerInfo) *envoy_listeners.ListenerBuilder {
+func GenerateListener(info GatewayListenerInfo) *envoy_listeners.ListenerBuilder {
 	// TODO(jpeach) what we really need to do here is to
 	// generate a HTTP filter chain for each
 	// host on the same HTTPConnectionManager. Each HTTP filter
diff --git a/pkg/plugins/runtime/gateway/plugin.go b/pkg/plugins/runtime/gateway/plugin.go
index d81039af2..11a259726 100644
--- a/pkg/plugins/runtime/gateway/plugin.go
+++ b/pkg/plugins/runtime/gateway/plugin.go
@@ -4,7 +4,6 @@ import (
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	config_core "github.com/kumahq/kuma/pkg/config/core"
 	"github.com/kumahq/kuma/pkg/core"
-	"github.com/kumahq/kuma/pkg/core/datasource"
 	core_plugins "github.com/kumahq/kuma/pkg/core/plugins"
 	mesh_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
 	"github.com/kumahq/kuma/pkg/plugins/runtime/gateway/register"
@@ -55,7 +54,7 @@ func (p *plugin) AfterBootstrap(context *core_plugins.MutablePluginContext, conf
 		generator.DefaultTemplateResolver,
 	)
 
-	generator.RegisterProfile(ProfileGatewayProxy, NewProxyProfile(context.Config().Multizone.Zone.Name, context.DataSourceLoader()))
+	generator.RegisterProfile(ProfileGatewayProxy, NewProxyProfile(context.Config().Multizone.Zone.Name))
 
 	log.Info("registered gateway plugin")
 	return nil
@@ -75,7 +74,7 @@ const ProfileGatewayProxy = "gateway-proxy"
 
 // NewProxyProfile returns a new resource generator profile for builtin
 // gateway dataplanes.
-func NewProxyProfile(zone string, dataSourceLoader datasource.Loader) generator.ResourceGenerator {
+func NewProxyProfile(zone string) generator.ResourceGenerator {
 	return generator.CompositeResourceGenerator{
 		generator.AdminProxyGenerator{},
 		generator.PrometheusEndpointGenerator{},
@@ -87,17 +86,13 @@ func NewProxyProfile(zone string, dataSourceLoader datasource.Loader) generator.
 		Generator{
 			FilterChainGenerators: filterChainGenerators{
 				FilterChainGenerators: map[mesh_proto.MeshGateway_Listener_Protocol]FilterChainGenerator{
-					mesh_proto.MeshGateway_Listener_HTTP: &HTTPFilterChainGenerator{},
-					mesh_proto.MeshGateway_Listener_HTTPS: &HTTPSFilterChainGenerator{
-						DataSourceLoader: dataSourceLoader,
-					},
+					mesh_proto.MeshGateway_Listener_HTTP:  &HTTPFilterChainGenerator{},
+					mesh_proto.MeshGateway_Listener_HTTPS: &HTTPSFilterChainGenerator{},
 				}},
 			ClusterGenerator: ClusterGenerator{
-				DataSourceLoader: dataSourceLoader,
-				Zone:             zone,
+				Zone: zone,
 			},
-			Zone:             zone,
-			DataSourceLoader: dataSourceLoader,
+			Zone: zone,
 		},
 	}
 }
diff --git a/pkg/plugins/runtime/k8s/controllers/configmap_controller.go b/pkg/plugins/runtime/k8s/controllers/configmap_controller.go
index 1ba1ddff0..068a593cc 100644
--- a/pkg/plugins/runtime/k8s/controllers/configmap_controller.go
+++ b/pkg/plugins/runtime/k8s/controllers/configmap_controller.go
@@ -7,6 +7,7 @@ import (
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 	kube_core "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/labels"
 	kube_runtime "k8s.io/apimachinery/pkg/runtime"
 	kube_types "k8s.io/apimachinery/pkg/types"
 	kube_record "k8s.io/client-go/tools/record"
@@ -23,7 +24,7 @@ import (
 	"github.com/kumahq/kuma/pkg/dns/vips"
 	k8s_common "github.com/kumahq/kuma/pkg/plugins/common/k8s"
 	mesh_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
-	"github.com/kumahq/kuma/pkg/plugins/runtime/k8s/metadata"
+	k8s_util "github.com/kumahq/kuma/pkg/plugins/runtime/k8s/util"
 )
 
 // ConfigMapReconciler reconciles a ConfigMap object
@@ -70,7 +71,7 @@ func (r *ConfigMapReconciler) SetupWithManager(mgr kube_ctrl.Manager) error {
 		Complete(r)
 }
 
-func ServiceToConfigMapsMapper(client kube_client.Reader, l logr.Logger, ns string) kube_handler.MapFunc {
+func ServiceToConfigMapsMapper(client kube_client.Reader, l logr.Logger, systemNamespace string) kube_handler.MapFunc {
 	l = l.WithName("service-to-configmap-mapper")
 	return func(obj kube_client.Object) []kube_reconile.Request {
 		cause, ok := obj.(*kube_core.Service)
@@ -83,21 +84,24 @@ func ServiceToConfigMapsMapper(client kube_client.Reader, l logr.Logger, ns stri
 		svcName := fmt.Sprintf("%s/%s", cause.Namespace, cause.Name)
 		// List Pods in the same namespace
 		pods := &kube_core.PodList{}
-		if err := client.List(ctx, pods, kube_client.InNamespace(obj.GetNamespace())); err != nil {
+		if err := client.List(ctx, pods, kube_client.InNamespace(obj.GetNamespace()), kube_client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(cause.Spec.Selector)}); err != nil {
 			l.WithValues("service", svcName).Error(err, "failed to fetch Dataplanes in namespace")
 			return nil
 		}
+		ns := kube_core.Namespace{}
+		if err := client.Get(ctx, kube_types.NamespacedName{Name: cause.Namespace}, &ns); err != nil {
+			l.WithValues("service", svcName).Error(err, "failed to fetch Namespace")
+			return nil
+		}
 
-		meshSet := map[string]bool{}
+		meshSet := map[string]struct{}{}
 		for _, pod := range pods.Items {
-			if mesh, exist := metadata.Annotations(pod.Annotations).GetString(metadata.KumaMeshAnnotation); exist {
-				meshSet[mesh] = true
-			}
+			meshSet[k8s_util.MeshOf(&pod, &ns)] = struct{}{}
 		}
 		var req []kube_reconile.Request
 		for mesh := range meshSet {
 			req = append(req, kube_reconile.Request{
-				NamespacedName: kube_types.NamespacedName{Namespace: ns, Name: vips.ConfigKey(mesh)},
+				NamespacedName: kube_types.NamespacedName{Namespace: systemNamespace, Name: vips.ConfigKey(mesh)},
 			})
 		}
 
diff --git a/pkg/plugins/runtime/k8s/controllers/gatewayapi/gateway_conversion.go b/pkg/plugins/runtime/k8s/controllers/gatewayapi/gateway_conversion.go
index 661ff2e1c..d56926202 100644
--- a/pkg/plugins/runtime/k8s/controllers/gatewayapi/gateway_conversion.go
+++ b/pkg/plugins/runtime/k8s/controllers/gatewayapi/gateway_conversion.go
@@ -16,12 +16,43 @@ import (
 
 type ListenerConditions map[gatewayapi.SectionName][]kube_meta.Condition
 
-func validateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener, ListenerConditions) {
+func validProtocol(protocol gatewayapi.ProtocolType) bool {
+	switch protocol {
+	case gatewayapi.HTTPProtocolType:
+		return true
+	case gatewayapi.HTTPSProtocolType:
+		// TODO HTTPS https://github.com/kumahq/kuma/issues/3679
+	default:
+	}
+
+	return false
+}
+
+func ValidateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener, ListenerConditions) {
 	var validListeners []gatewayapi.Listener
 	listenerConditions := ListenerConditions{}
 
-	portHostnames := map[gatewayapi.PortNumber]gatewayapi.Hostname{}
-	portProtocols := map[gatewayapi.PortNumber]gatewayapi.ProtocolType{}
+	appendDetachedCondition := func(
+		listener gatewayapi.SectionName,
+		reason gatewayapi.ListenerConditionReason,
+		message string,
+	) {
+		listenerConditions[listener] = append(
+			listenerConditions[listener],
+			kube_meta.Condition{
+				Type:    string(gatewayapi.ListenerConditionDetached),
+				Status:  kube_meta.ConditionTrue,
+				Reason:  string(reason),
+				Message: message,
+			},
+			kube_meta.Condition{
+				Type:    string(gatewayapi.ListenerConditionReady),
+				Status:  kube_meta.ConditionFalse,
+				Reason:  string(gatewayapi.ListenerReasonInvalid),
+				Message: "detached",
+			},
+		)
+	}
 
 	appendConflictedCondition := func(
 		listener gatewayapi.SectionName,
@@ -45,9 +76,55 @@ func validateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener,
 		)
 	}
 
+	// Collect information about used hostnames and protocols
+	// We can only have at most one listener for each HostnamePort
+	type HostnamePort struct {
+		hostname gatewayapi.Hostname
+		port     gatewayapi.PortNumber
+	}
+	portHostnames := map[HostnamePort]int{}
+	// We can only have one protocol for each port
+	portProtocols := map[gatewayapi.PortNumber]map[gatewayapi.ProtocolType]struct{}{}
+
+	for _, l := range listeners {
+		if hn := l.Hostname; hn != nil {
+			hostnamePort := HostnamePort{
+				hostname: *hn,
+				port:     l.Port,
+			}
+			portHostnames[hostnamePort]++
+		}
+
+		protocols, ok := portProtocols[l.Port]
+		if !ok {
+			protocols = map[gatewayapi.ProtocolType]struct{}{}
+		}
+
+		if validProtocol(l.Protocol) {
+			protocols[l.Protocol] = struct{}{}
+		}
+		portProtocols[l.Port] = protocols
+	}
+
 	for _, l := range listeners {
+		if !validProtocol(l.Protocol) {
+			appendDetachedCondition(
+				l.Name,
+				gatewayapi.ListenerReasonUnsupportedProtocol,
+				fmt.Sprintf("unsupported protocol %s", l.Protocol),
+			)
+			continue
+		}
+
+		// TODO ListenerReasonUnsupportedAddress and ListenerReasonPortUnavailable
+		// need more information from Envoy Gateway
+
 		if hn := l.Hostname; hn != nil {
-			if otherHn := portHostnames[l.Port]; otherHn == *hn {
+			hostnamePort := HostnamePort{
+				hostname: *hn,
+				port:     l.Port,
+			}
+			if num := portHostnames[hostnamePort]; num > 1 {
 				appendConflictedCondition(
 					l.Name,
 					gatewayapi.ListenerReasonHostnameConflict,
@@ -55,18 +132,16 @@ func validateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener,
 				)
 				continue
 			}
-			portHostnames[l.Port] = *hn
 		}
 
-		if otherProtocol, ok := portProtocols[l.Port]; ok && otherProtocol != l.Protocol {
+		if protocols := portProtocols[l.Port]; len(protocols) > 1 {
 			appendConflictedCondition(
 				l.Name,
 				gatewayapi.ListenerReasonProtocolConflict,
-				fmt.Sprintf("multiple listeners on %d with conflicting protocols %s and %s", l.Port, otherProtocol, l.Protocol),
+				fmt.Sprintf("multiple listeners on %d with conflicting protocols", l.Port),
 			)
 			continue
 		}
-		portProtocols[l.Port] = l.Protocol
 
 		// We don't set ListenerReasonRouteConflict because we already check the
 		// routes with ListenerReasonInvalidRouteKinds
@@ -84,7 +159,7 @@ func validateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener,
 func (r *GatewayReconciler) gapiToKumaGateway(
 	ctx context.Context, gateway *gatewayapi.Gateway,
 ) (*mesh_proto.MeshGateway, ListenerConditions, error) {
-	validListeners, listenerConditions := validateListeners(gateway.Spec.Listeners)
+	validListeners, listenerConditions := ValidateListeners(gateway.Spec.Listeners)
 
 	var listeners []*mesh_proto.MeshGateway_Listener
 
@@ -154,9 +229,14 @@ func (r *GatewayReconciler) gapiToKumaGateway(
 			}
 		}
 
-		// We've already cleared this listener of conflicts
+		// We've already cleared this listener of conflicts and being detached
 		listenerConditions[l.Name] = append(
 			listenerConditions[l.Name],
+			kube_meta.Condition{
+				Type:   string(gatewayapi.ListenerConditionDetached),
+				Status: kube_meta.ConditionFalse,
+				Reason: string(gatewayapi.ListenerReasonAttached),
+			},
 			kube_meta.Condition{
 				Type:   string(gatewayapi.ListenerConditionConflicted),
 				Status: kube_meta.ConditionFalse,


Test patch:
diff --git a/pkg/plugins/runtime/gateway/gateway_route_generator_test.go b/pkg/plugins/runtime/gateway/gateway_route_generator_test.go
index 11d0f58fe..25c2e0faa 100644
--- a/pkg/plugins/runtime/gateway/gateway_route_generator_test.go
+++ b/pkg/plugins/runtime/gateway/gateway_route_generator_test.go
@@ -1008,6 +1008,185 @@ conf:
   http:
     requests: 1
     interval: 30s
+`,
+		),
+
+		Entry("should distribute routes across wildcard listener",
+			"22-gateway-route.yaml", `
+type: MeshGateway
+mesh: default
+name: edge-gateway
+selectors:
+- match:
+    kuma.io/service: gateway-default
+conf:
+  listeners:
+  - port: 8080
+    protocol: HTTP
+    hostname: example.com
+    tags:
+      hostname: example.com
+      port: http/8080
+  - port: 8080
+    protocol: HTTP
+    tags:
+      hostname: all
+      port: http/8080
+`, `
+type: MeshGatewayRoute
+mesh: default
+name: echo-service
+selectors:
+- match:
+    kuma.io/service: gateway-default
+    hostname: example.com
+conf:
+  http:
+    rules:
+    - matches:
+      - path:
+          match: EXACT
+          value: /v2
+      backends:
+      - destination:
+          kuma.io/service: api-service
+`, `
+type: MeshGatewayRoute
+mesh: default
+name: echo-service-extra
+selectors:
+- match:
+    kuma.io/service: gateway-default
+    hostname: all
+conf:
+  http:
+    hostnames:
+    - "example.com"
+    rules:
+    - matches:
+      - path:
+          match: EXACT
+          value: /
+      backends:
+      - destination:
+          kuma.io/service: echo-service
+`,
+		),
+
+		Entry("should distribute partial matching routes across wildcard listener",
+			"23-gateway-route.yaml", `
+type: MeshGateway
+mesh: default
+name: edge-gateway
+selectors:
+- match:
+    kuma.io/service: gateway-default
+conf:
+  listeners:
+  - port: 8080
+    protocol: HTTP
+    hostname: example.com
+    tags:
+      hostname: example.com
+      port: http/8080
+  - port: 8080
+    protocol: HTTP
+    tags:
+      hostname: all
+      port: http/8080
+`, `
+type: MeshGatewayRoute
+mesh: default
+name: echo-service
+selectors:
+- match:
+    kuma.io/service: gateway-default
+    hostname: example.com
+conf:
+  http:
+    rules:
+    - matches:
+      - path:
+          match: EXACT
+          value: /v2
+      backends:
+      - destination:
+          kuma.io/service: api-service
+`, `
+type: MeshGatewayRoute
+mesh: default
+name: echo-service-extra
+selectors:
+- match:
+    kuma.io/service: gateway-default
+    hostname: all
+conf:
+  http:
+    hostnames:
+    - "*.com"
+    rules:
+    - matches:
+      - path:
+          match: EXACT
+          value: /
+      backends:
+      - destination:
+          kuma.io/service: echo-service
+`,
+		),
+
+		Entry("should distribute partial matching routes for one listener",
+			"24-gateway-route.yaml", `
+type: MeshGateway
+mesh: default
+name: edge-gateway
+selectors:
+- match:
+    kuma.io/service: gateway-default
+conf:
+  listeners:
+  - port: 8080
+    protocol: HTTP
+    tags:
+      port: http/8080
+`, `
+type: MeshGatewayRoute
+mesh: default
+name: echo-service
+selectors:
+- match:
+    kuma.io/service: gateway-default
+conf:
+  http:
+    hostnames:
+    - "example.com"
+    rules:
+    - matches:
+      - path:
+          match: EXACT
+          value: /v2
+      backends:
+      - destination:
+          kuma.io/service: api-service
+`, `
+type: MeshGatewayRoute
+mesh: default
+name: echo-service-extra
+selectors:
+- match:
+    kuma.io/service: gateway-default
+conf:
+  http:
+    hostnames:
+    - "*.com"
+    rules:
+    - matches:
+      - path:
+          match: EXACT
+          value: /
+      backends:
+      - destination:
+          kuma.io/service: echo-service
 `,
 		),
 	}
diff --git a/pkg/plugins/runtime/gateway/testdata/http/22-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/22-gateway-route.yaml
new file mode 100644
index 000000000..3c5bdc6fd
--- /dev/null
+++ b/pkg/plugins/runtime/gateway/testdata/http/22-gateway-route.yaml
@@ -0,0 +1,196 @@
+Clusters:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: api-service-f8ae3d759cc477a6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+    echo-service-5a416c39037aa8f6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: echo-service-5a416c39037aa8f6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+Endpoints:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      clusterName: api-service-f8ae3d759cc477a6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.1
+                portValue: 20001
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+    echo-service-5a416c39037aa8f6:
+      clusterName: echo-service-5a416c39037aa8f6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.6
+                portValue: 20006
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+Listeners:
+  Resources:
+    edge-gateway:HTTP:8080:
+      address:
+        socketAddress:
+          address: 192.168.1.1
+          portValue: 8080
+      filterChains:
+      - filters:
+        - name: envoy.filters.network.http_connection_manager
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+            commonHttpProtocolOptions:
+              headersWithUnderscoresAction: REJECT_REQUEST
+              idleTimeout: 300s
+            http2ProtocolOptions:
+              allowConnect: true
+              initialConnectionWindowSize: 1048576
+              initialStreamWindowSize: 65536
+              maxConcurrentStreams: 100
+            httpFilters:
+            - name: envoy.filters.http.local_ratelimit
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                statPrefix: rate_limit
+            - name: gzip-compress
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
+                compressorLibrary:
+                  name: gzip
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
+                responseDirectionConfig:
+                  disableOnEtagHeader: true
+            - name: envoy.filters.http.router
+            mergeSlashes: true
+            normalizePath: true
+            rds:
+              configSource:
+                ads: {}
+                resourceApiVersion: V3
+              routeConfigName: edge-gateway:HTTP:8080
+            requestHeadersTimeout: 0.500s
+            serverName: Kuma Gateway
+            statPrefix: gateway-default
+            streamIdleTimeout: 5s
+            stripAnyHostPort: true
+      listenerFilters:
+      - name: envoy.filters.listener.tls_inspector
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+      name: edge-gateway:HTTP:8080
+      perConnectionBufferLimitBytes: 32768
+      reusePort: true
+      trafficDirection: INBOUND
+Routes:
+  Resources:
+    edge-gateway:HTTP:8080:
+      name: edge-gateway:HTTP:8080
+      requestHeadersToRemove:
+      - x-kuma-tags
+      validateClusters: false
+      virtualHosts:
+      - domains:
+        - example.com
+        name: example.com
+        routes:
+        - match:
+            path: /v2
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: api-service-f8ae3d759cc477a6
+                weight: 1
+              totalWeight: 1
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*'
+        name: '*'
+Runtimes:
+  Resources: {}
+Secrets:
+  Resources: {}
diff --git a/pkg/plugins/runtime/gateway/testdata/http/23-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/23-gateway-route.yaml
new file mode 100644
index 000000000..c25bab620
--- /dev/null
+++ b/pkg/plugins/runtime/gateway/testdata/http/23-gateway-route.yaml
@@ -0,0 +1,216 @@
+Clusters:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: api-service-f8ae3d759cc477a6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+    echo-service-5a416c39037aa8f6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: echo-service-5a416c39037aa8f6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+Endpoints:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      clusterName: api-service-f8ae3d759cc477a6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.1
+                portValue: 20001
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+    echo-service-5a416c39037aa8f6:
+      clusterName: echo-service-5a416c39037aa8f6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.6
+                portValue: 20006
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+Listeners:
+  Resources:
+    edge-gateway:HTTP:8080:
+      address:
+        socketAddress:
+          address: 192.168.1.1
+          portValue: 8080
+      filterChains:
+      - filters:
+        - name: envoy.filters.network.http_connection_manager
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+            commonHttpProtocolOptions:
+              headersWithUnderscoresAction: REJECT_REQUEST
+              idleTimeout: 300s
+            http2ProtocolOptions:
+              allowConnect: true
+              initialConnectionWindowSize: 1048576
+              initialStreamWindowSize: 65536
+              maxConcurrentStreams: 100
+            httpFilters:
+            - name: envoy.filters.http.local_ratelimit
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                statPrefix: rate_limit
+            - name: gzip-compress
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
+                compressorLibrary:
+                  name: gzip
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
+                responseDirectionConfig:
+                  disableOnEtagHeader: true
+            - name: envoy.filters.http.router
+            mergeSlashes: true
+            normalizePath: true
+            rds:
+              configSource:
+                ads: {}
+                resourceApiVersion: V3
+              routeConfigName: edge-gateway:HTTP:8080
+            requestHeadersTimeout: 0.500s
+            serverName: Kuma Gateway
+            statPrefix: gateway-default
+            streamIdleTimeout: 5s
+            stripAnyHostPort: true
+      listenerFilters:
+      - name: envoy.filters.listener.tls_inspector
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+      name: edge-gateway:HTTP:8080
+      perConnectionBufferLimitBytes: 32768
+      reusePort: true
+      trafficDirection: INBOUND
+Routes:
+  Resources:
+    edge-gateway:HTTP:8080:
+      name: edge-gateway:HTTP:8080
+      requestHeadersToRemove:
+      - x-kuma-tags
+      validateClusters: false
+      virtualHosts:
+      - domains:
+        - example.com
+        name: example.com
+        routes:
+        - match:
+            path: /v2
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: api-service-f8ae3d759cc477a6
+                weight: 1
+              totalWeight: 1
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*.com'
+        name: '*.com'
+        routes:
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*'
+        name: '*'
+Runtimes:
+  Resources: {}
+Secrets:
+  Resources: {}
diff --git a/pkg/plugins/runtime/gateway/testdata/http/24-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/24-gateway-route.yaml
new file mode 100644
index 000000000..c25bab620
--- /dev/null
+++ b/pkg/plugins/runtime/gateway/testdata/http/24-gateway-route.yaml
@@ -0,0 +1,216 @@
+Clusters:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: api-service-f8ae3d759cc477a6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+    echo-service-5a416c39037aa8f6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: echo-service-5a416c39037aa8f6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+Endpoints:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      clusterName: api-service-f8ae3d759cc477a6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.1
+                portValue: 20001
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+    echo-service-5a416c39037aa8f6:
+      clusterName: echo-service-5a416c39037aa8f6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.6
+                portValue: 20006
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+Listeners:
+  Resources:
+    edge-gateway:HTTP:8080:
+      address:
+        socketAddress:
+          address: 192.168.1.1
+          portValue: 8080
+      filterChains:
+      - filters:
+        - name: envoy.filters.network.http_connection_manager
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+            commonHttpProtocolOptions:
+              headersWithUnderscoresAction: REJECT_REQUEST
+              idleTimeout: 300s
+            http2ProtocolOptions:
+              allowConnect: true
+              initialConnectionWindowSize: 1048576
+              initialStreamWindowSize: 65536
+              maxConcurrentStreams: 100
+            httpFilters:
+            - name: envoy.filters.http.local_ratelimit
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                statPrefix: rate_limit
+            - name: gzip-compress
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
+                compressorLibrary:
+                  name: gzip
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
+                responseDirectionConfig:
+                  disableOnEtagHeader: true
+            - name: envoy.filters.http.router
+            mergeSlashes: true
+            normalizePath: true
+            rds:
+              configSource:
+                ads: {}
+                resourceApiVersion: V3
+              routeConfigName: edge-gateway:HTTP:8080
+            requestHeadersTimeout: 0.500s
+            serverName: Kuma Gateway
+            statPrefix: gateway-default
+            streamIdleTimeout: 5s
+            stripAnyHostPort: true
+      listenerFilters:
+      - name: envoy.filters.listener.tls_inspector
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+      name: edge-gateway:HTTP:8080
+      perConnectionBufferLimitBytes: 32768
+      reusePort: true
+      trafficDirection: INBOUND
+Routes:
+  Resources:
+    edge-gateway:HTTP:8080:
+      name: edge-gateway:HTTP:8080
+      requestHeadersToRemove:
+      - x-kuma-tags
+      validateClusters: false
+      virtualHosts:
+      - domains:
+        - example.com
+        name: example.com
+        routes:
+        - match:
+            path: /v2
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: api-service-f8ae3d759cc477a6
+                weight: 1
+              totalWeight: 1
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*.com'
+        name: '*.com'
+        routes:
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*'
+        name: '*'
+Runtimes:
+  Resources: {}
+Secrets:
+  Resources: {}
diff --git a/pkg/plugins/runtime/gateway/testdata/https/22-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/22-gateway-route.yaml
new file mode 100644
index 000000000..3c5bdc6fd
--- /dev/null
+++ b/pkg/plugins/runtime/gateway/testdata/https/22-gateway-route.yaml
@@ -0,0 +1,196 @@
+Clusters:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: api-service-f8ae3d759cc477a6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+    echo-service-5a416c39037aa8f6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: echo-service-5a416c39037aa8f6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+Endpoints:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      clusterName: api-service-f8ae3d759cc477a6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.1
+                portValue: 20001
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+    echo-service-5a416c39037aa8f6:
+      clusterName: echo-service-5a416c39037aa8f6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.6
+                portValue: 20006
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+Listeners:
+  Resources:
+    edge-gateway:HTTP:8080:
+      address:
+        socketAddress:
+          address: 192.168.1.1
+          portValue: 8080
+      filterChains:
+      - filters:
+        - name: envoy.filters.network.http_connection_manager
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+            commonHttpProtocolOptions:
+              headersWithUnderscoresAction: REJECT_REQUEST
+              idleTimeout: 300s
+            http2ProtocolOptions:
+              allowConnect: true
+              initialConnectionWindowSize: 1048576
+              initialStreamWindowSize: 65536
+              maxConcurrentStreams: 100
+            httpFilters:
+            - name: envoy.filters.http.local_ratelimit
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                statPrefix: rate_limit
+            - name: gzip-compress
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
+                compressorLibrary:
+                  name: gzip
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
+                responseDirectionConfig:
+                  disableOnEtagHeader: true
+            - name: envoy.filters.http.router
+            mergeSlashes: true
+            normalizePath: true
+            rds:
+              configSource:
+                ads: {}
+                resourceApiVersion: V3
+              routeConfigName: edge-gateway:HTTP:8080
+            requestHeadersTimeout: 0.500s
+            serverName: Kuma Gateway
+            statPrefix: gateway-default
+            streamIdleTimeout: 5s
+            stripAnyHostPort: true
+      listenerFilters:
+      - name: envoy.filters.listener.tls_inspector
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+      name: edge-gateway:HTTP:8080
+      perConnectionBufferLimitBytes: 32768
+      reusePort: true
+      trafficDirection: INBOUND
+Routes:
+  Resources:
+    edge-gateway:HTTP:8080:
+      name: edge-gateway:HTTP:8080
+      requestHeadersToRemove:
+      - x-kuma-tags
+      validateClusters: false
+      virtualHosts:
+      - domains:
+        - example.com
+        name: example.com
+        routes:
+        - match:
+            path: /v2
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: api-service-f8ae3d759cc477a6
+                weight: 1
+              totalWeight: 1
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*'
+        name: '*'
+Runtimes:
+  Resources: {}
+Secrets:
+  Resources: {}
diff --git a/pkg/plugins/runtime/gateway/testdata/https/23-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/23-gateway-route.yaml
new file mode 100644
index 000000000..c25bab620
--- /dev/null
+++ b/pkg/plugins/runtime/gateway/testdata/https/23-gateway-route.yaml
@@ -0,0 +1,216 @@
+Clusters:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: api-service-f8ae3d759cc477a6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+    echo-service-5a416c39037aa8f6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: echo-service-5a416c39037aa8f6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+Endpoints:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      clusterName: api-service-f8ae3d759cc477a6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.1
+                portValue: 20001
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+    echo-service-5a416c39037aa8f6:
+      clusterName: echo-service-5a416c39037aa8f6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.6
+                portValue: 20006
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+Listeners:
+  Resources:
+    edge-gateway:HTTP:8080:
+      address:
+        socketAddress:
+          address: 192.168.1.1
+          portValue: 8080
+      filterChains:
+      - filters:
+        - name: envoy.filters.network.http_connection_manager
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+            commonHttpProtocolOptions:
+              headersWithUnderscoresAction: REJECT_REQUEST
+              idleTimeout: 300s
+            http2ProtocolOptions:
+              allowConnect: true
+              initialConnectionWindowSize: 1048576
+              initialStreamWindowSize: 65536
+              maxConcurrentStreams: 100
+            httpFilters:
+            - name: envoy.filters.http.local_ratelimit
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                statPrefix: rate_limit
+            - name: gzip-compress
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
+                compressorLibrary:
+                  name: gzip
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
+                responseDirectionConfig:
+                  disableOnEtagHeader: true
+            - name: envoy.filters.http.router
+            mergeSlashes: true
+            normalizePath: true
+            rds:
+              configSource:
+                ads: {}
+                resourceApiVersion: V3
+              routeConfigName: edge-gateway:HTTP:8080
+            requestHeadersTimeout: 0.500s
+            serverName: Kuma Gateway
+            statPrefix: gateway-default
+            streamIdleTimeout: 5s
+            stripAnyHostPort: true
+      listenerFilters:
+      - name: envoy.filters.listener.tls_inspector
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+      name: edge-gateway:HTTP:8080
+      perConnectionBufferLimitBytes: 32768
+      reusePort: true
+      trafficDirection: INBOUND
+Routes:
+  Resources:
+    edge-gateway:HTTP:8080:
+      name: edge-gateway:HTTP:8080
+      requestHeadersToRemove:
+      - x-kuma-tags
+      validateClusters: false
+      virtualHosts:
+      - domains:
+        - example.com
+        name: example.com
+        routes:
+        - match:
+            path: /v2
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: api-service-f8ae3d759cc477a6
+                weight: 1
+              totalWeight: 1
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*.com'
+        name: '*.com'
+        routes:
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*'
+        name: '*'
+Runtimes:
+  Resources: {}
+Secrets:
+  Resources: {}
diff --git a/pkg/plugins/runtime/gateway/testdata/https/24-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/24-gateway-route.yaml
new file mode 100644
index 000000000..c25bab620
--- /dev/null
+++ b/pkg/plugins/runtime/gateway/testdata/https/24-gateway-route.yaml
@@ -0,0 +1,216 @@
+Clusters:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: api-service-f8ae3d759cc477a6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+    echo-service-5a416c39037aa8f6:
+      circuitBreakers:
+        thresholds:
+        - maxConnections: 1024
+          maxPendingRequests: 1024
+          maxRequests: 1024
+          maxRetries: 3
+      connectTimeout: 5s
+      edsClusterConfig:
+        edsConfig:
+          ads: {}
+          resourceApiVersion: V3
+      name: echo-service-5a416c39037aa8f6
+      outlierDetection:
+        enforcingConsecutive5xx: 0
+        enforcingConsecutiveGatewayFailure: 0
+        enforcingConsecutiveLocalOriginFailure: 0
+        enforcingFailurePercentage: 0
+        enforcingSuccessRate: 0
+      type: EDS
+      typedExtensionProtocolOptions:
+        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+          '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+          commonHttpProtocolOptions:
+            idleTimeout: 3600s
+          explicitHttpConfig:
+            httpProtocolOptions: {}
+Endpoints:
+  Resources:
+    api-service-f8ae3d759cc477a6:
+      clusterName: api-service-f8ae3d759cc477a6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.1
+                portValue: 20001
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+    echo-service-5a416c39037aa8f6:
+      clusterName: echo-service-5a416c39037aa8f6
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.1.6
+                portValue: 20006
+          loadBalancingWeight: 1
+          metadata:
+            filterMetadata:
+              envoy.lb:
+                kuma.io/protocol: http
+              envoy.transport_socket_match:
+                kuma.io/protocol: http
+Listeners:
+  Resources:
+    edge-gateway:HTTP:8080:
+      address:
+        socketAddress:
+          address: 192.168.1.1
+          portValue: 8080
+      filterChains:
+      - filters:
+        - name: envoy.filters.network.http_connection_manager
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+            commonHttpProtocolOptions:
+              headersWithUnderscoresAction: REJECT_REQUEST
+              idleTimeout: 300s
+            http2ProtocolOptions:
+              allowConnect: true
+              initialConnectionWindowSize: 1048576
+              initialStreamWindowSize: 65536
+              maxConcurrentStreams: 100
+            httpFilters:
+            - name: envoy.filters.http.local_ratelimit
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                statPrefix: rate_limit
+            - name: gzip-compress
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
+                compressorLibrary:
+                  name: gzip
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
+                responseDirectionConfig:
+                  disableOnEtagHeader: true
+            - name: envoy.filters.http.router
+            mergeSlashes: true
+            normalizePath: true
+            rds:
+              configSource:
+                ads: {}
+                resourceApiVersion: V3
+              routeConfigName: edge-gateway:HTTP:8080
+            requestHeadersTimeout: 0.500s
+            serverName: Kuma Gateway
+            statPrefix: gateway-default
+            streamIdleTimeout: 5s
+            stripAnyHostPort: true
+      listenerFilters:
+      - name: envoy.filters.listener.tls_inspector
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+      name: edge-gateway:HTTP:8080
+      perConnectionBufferLimitBytes: 32768
+      reusePort: true
+      trafficDirection: INBOUND
+Routes:
+  Resources:
+    edge-gateway:HTTP:8080:
+      name: edge-gateway:HTTP:8080
+      requestHeadersToRemove:
+      - x-kuma-tags
+      validateClusters: false
+      virtualHosts:
+      - domains:
+        - example.com
+        name: example.com
+        routes:
+        - match:
+            path: /v2
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: api-service-f8ae3d759cc477a6
+                weight: 1
+              totalWeight: 1
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*.com'
+        name: '*.com'
+        routes:
+        - match:
+            path: /
+          route:
+            retryPolicy:
+              numRetries: 5
+              perTryTimeout: 16s
+              retryBackOff:
+                baseInterval: 0.025s
+                maxInterval: 0.250s
+              retryOn: gateway-error,connect-failure,refused-stream
+            timeout: 15s
+            weightedClusters:
+              clusters:
+              - name: echo-service-5a416c39037aa8f6
+                weight: 1
+              totalWeight: 1
+      - domains:
+        - '*'
+        name: '*'
+Runtimes:
+  Resources: {}
+Secrets:
+  Resources: {}
diff --git a/pkg/plugins/runtime/k8s/controllers/configmap_controller_test.go b/pkg/plugins/runtime/k8s/controllers/configmap_controller_test.go
index 9b1746e06..b36710562 100644
--- a/pkg/plugins/runtime/k8s/controllers/configmap_controller_test.go
+++ b/pkg/plugins/runtime/k8s/controllers/configmap_controller_test.go
@@ -1,19 +1,29 @@
-package controllers
+package controllers_test
 
 import (
+	"context"
+	"fmt"
+	"sort"
+	"time"
+
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
+	kube_core "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kube_client "sigs.k8s.io/controller-runtime/pkg/client"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	"github.com/kumahq/kuma/pkg/log"
 	"github.com/kumahq/kuma/pkg/plugins/resources/k8s"
 	mesh_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
+	"github.com/kumahq/kuma/pkg/plugins/runtime/k8s/controllers"
+	"github.com/kumahq/kuma/pkg/plugins/runtime/k8s/metadata"
 )
 
 var _ = Describe("DataplaneToMeshMapper", func() {
 	It("should map ingress to list of meshes", func() {
 		l := log.NewLogger(log.InfoLevel)
-		mapper := DataplaneToMeshMapper(l, "ns", k8s.NewSimpleConverter())
+		mapper := controllers.DataplaneToMeshMapper(l, "ns", k8s.NewSimpleConverter())
 		requests := mapper(&mesh_k8s.Dataplane{
 			Mesh: "mesh-1",
 			Spec: mesh_k8s.ToSpec(&mesh_proto.Dataplane{
@@ -36,3 +46,134 @@ var _ = Describe("DataplaneToMeshMapper", func() {
 		Expect(requestsStr).To(ConsistOf("kuma-mesh-1-dns-vips"))
 	})
 })
+
+var _ = Describe("ServiceToConfigMapMapper", func() {
+	ctx := context.Background()
+	var nsName string
+	defaultNs := kube_core.Namespace{
+		ObjectMeta: metav1.ObjectMeta{},
+	}
+	AfterEach(func() {
+		ns := kube_core.Namespace{}
+		key := kube_client.ObjectKey{Name: nsName}
+		Expect(k8sClient.Get(ctx, key, &ns)).To(Succeed())
+		Expect(k8sClient.Delete(ctx, &ns)).To(Succeed())
+	})
+	BeforeEach(func() {
+		nsName = fmt.Sprintf("srv-to-configmap-mapper-%d", time.Now().UnixMilli())
+	})
+	serviceFn := func(selector map[string]string) kube_core.Service {
+		return kube_core.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "svc",
+			},
+			Spec: kube_core.ServiceSpec{
+				Selector: selector,
+				Ports: []kube_core.ServicePort{
+					{Port: 80},
+				},
+			},
+		}
+	}
+	podFn := func(name string, labels map[string]string, annotations map[string]string) kube_core.Pod {
+		return kube_core.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:        name,
+				Labels:      labels,
+				Annotations: annotations,
+			},
+			Spec: kube_core.PodSpec{
+				Containers: []kube_core.Container{
+					{Name: "foo", Image: "busybox"},
+				},
+			},
+		}
+	}
+	DescribeTable("should map services to list of config maps",
+		func(givenService kube_core.Service, givenNamespace kube_core.Namespace, givenPods []kube_core.Pod, expectedMeshes []string) {
+			l := log.NewLogger(log.InfoLevel)
+			givenNamespace.Name = nsName
+			Expect(k8sClient.Create(ctx, &givenNamespace)).To(Succeed())
+			givenService.Namespace = givenNamespace.Name
+			Expect(k8sClient.Create(ctx, &givenService)).To(Succeed())
+			for _, pod := range givenPods {
+				pod.Namespace = givenNamespace.Name
+				Expect(k8sClient.Create(ctx, &pod)).To(Succeed())
+			}
+			mapper := controllers.ServiceToConfigMapsMapper(k8sClient, l, "ns")
+			requests := mapper(&givenService)
+			requestsStr := []string{}
+			for _, r := range requests {
+				requestsStr = append(requestsStr, r.Name)
+			}
+			sort.Strings(requestsStr)
+			Expect(requestsStr).To(Equal(expectedMeshes))
+		},
+		Entry("no pod match",
+			serviceFn(map[string]string{"app": "app1"}),
+			defaultNs,
+			[]kube_core.Pod{
+				podFn("pod1", map[string]string{"app": "app2"}, nil),
+				podFn("pod2", map[string]string{"app": "app2"}, map[string]string{metadata.KumaMeshAnnotation: "mesh2"}),
+			},
+			[]string{},
+		),
+		Entry("namespace not annotated selects only matching pods",
+			serviceFn(map[string]string{"app": "app1"}),
+			defaultNs,
+			[]kube_core.Pod{
+				podFn("pod1", map[string]string{"app": "app1"}, nil),
+				podFn("pod2", map[string]string{"app": "app2"}, map[string]string{metadata.KumaMeshAnnotation: "mesh2"}),
+			},
+			[]string{"kuma-default-dns-vips"},
+		),
+		Entry("namespace not annotated pod annotated selects only matching pods",
+			serviceFn(map[string]string{"app": "app1"}),
+			defaultNs,
+			[]kube_core.Pod{
+				podFn("pod1", map[string]string{"app": "app1"}, map[string]string{metadata.KumaMeshAnnotation: "mesh1"}),
+				podFn("pod2", map[string]string{"app": "app2"}, map[string]string{metadata.KumaMeshAnnotation: "mesh2"}),
+			},
+			[]string{"kuma-mesh1-dns-vips"},
+		),
+		Entry("namespace not annotated pod annotated matches on multiple meshes",
+			serviceFn(map[string]string{"app": "app1"}),
+			defaultNs,
+			[]kube_core.Pod{
+				podFn("pod1", map[string]string{"app": "app1"}, map[string]string{metadata.KumaMeshAnnotation: "mesh1"}),
+				podFn("pod2", map[string]string{"app": "app1"}, map[string]string{metadata.KumaMeshAnnotation: "mesh2"}),
+			},
+			[]string{"kuma-mesh1-dns-vips", "kuma-mesh2-dns-vips"},
+		),
+		Entry("namespace annotated pod not annotated",
+			serviceFn(map[string]string{"app": "app1"}),
+			kube_core.Namespace{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						metadata.KumaMeshAnnotation: "mesh1",
+					},
+				},
+			},
+			[]kube_core.Pod{
+				podFn("pod1", map[string]string{"app": "app1"}, nil),
+				podFn("pod2", map[string]string{"app": "app1"}, nil),
+			},
+			[]string{"kuma-mesh1-dns-vips"},
+		),
+		Entry("namespace annotated pod annotated",
+			serviceFn(map[string]string{"app": "app1"}),
+			kube_core.Namespace{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						metadata.KumaMeshAnnotation: "mesh1",
+					},
+				},
+			},
+			[]kube_core.Pod{
+				podFn("pod1", map[string]string{"app": "app1"}, map[string]string{metadata.KumaMeshAnnotation: "mesh2"}),
+				podFn("pod2", map[string]string{"app": "app1"}, nil),
+			},
+			[]string{"kuma-mesh1-dns-vips", "kuma-mesh2-dns-vips"},
+		),
+	)
+})
diff --git a/pkg/plugins/runtime/k8s/controllers/gatewayapi/gateway_conversion_test.go b/pkg/plugins/runtime/k8s/controllers/gatewayapi/gateway_conversion_test.go
new file mode 100644
index 000000000..ad9dedabc
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/controllers/gatewayapi/gateway_conversion_test.go
@@ -0,0 +1,129 @@
+package gatewayapi_test
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	gatewayapi "sigs.k8s.io/gateway-api/apis/v1alpha2"
+
+	k8s_gatewayapi "github.com/kumahq/kuma/pkg/plugins/runtime/k8s/controllers/gatewayapi"
+)
+
+var _ = Describe("ValidateListeners", func() {
+	It("works with one simple listener", func() {
+		same := gatewayapi.NamespacesFromSame
+		listeners := []gatewayapi.Listener{
+			{
+				Name:     gatewayapi.SectionName("prod"),
+				Protocol: gatewayapi.HTTPProtocolType,
+				Port:     gatewayapi.PortNumber(80),
+				AllowedRoutes: &gatewayapi.AllowedRoutes{
+					Namespaces: &gatewayapi.RouteNamespaces{
+						From: &same,
+					},
+				},
+			},
+		}
+		valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
+		Expect(valids).To(ConsistOf(
+			HaveField("Name", gatewayapi.SectionName("prod")),
+		))
+		Expect(conditions).To(BeEmpty())
+	})
+	It("works with conflicting protocols", func() {
+		same := gatewayapi.NamespacesFromSame
+		listeners := []gatewayapi.Listener{
+			{
+				Name:     gatewayapi.SectionName("prod-1"),
+				Protocol: gatewayapi.HTTPProtocolType,
+				Port:     gatewayapi.PortNumber(80),
+				AllowedRoutes: &gatewayapi.AllowedRoutes{
+					Namespaces: &gatewayapi.RouteNamespaces{
+						From: &same,
+					},
+				},
+			},
+			{
+				Name:     gatewayapi.SectionName("prod-2"),
+				Protocol: gatewayapi.HTTPSProtocolType,
+				Port:     gatewayapi.PortNumber(80),
+				AllowedRoutes: &gatewayapi.AllowedRoutes{
+					Namespaces: &gatewayapi.RouteNamespaces{
+						From: &same,
+					},
+				},
+			},
+		}
+		valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
+		Expect(valids).To(ConsistOf(
+			HaveField("Name", gatewayapi.SectionName("prod-1")),
+		))
+		Expect(conditions).To(HaveKey(gatewayapi.SectionName("prod-2")))
+	})
+	It("works with differing hostnames", func() {
+		same := gatewayapi.NamespacesFromSame
+		foo := gatewayapi.Hostname("foo.com")
+		bar := gatewayapi.Hostname("bar.com")
+		listeners := []gatewayapi.Listener{
+			{
+				Name:     gatewayapi.SectionName("prod-1"),
+				Protocol: gatewayapi.HTTPProtocolType,
+				Hostname: &foo,
+				Port:     gatewayapi.PortNumber(80),
+				AllowedRoutes: &gatewayapi.AllowedRoutes{
+					Namespaces: &gatewayapi.RouteNamespaces{
+						From: &same,
+					},
+				},
+			},
+			{
+				Name:     gatewayapi.SectionName("prod-2"),
+				Protocol: gatewayapi.HTTPProtocolType,
+				Hostname: &bar,
+				Port:     gatewayapi.PortNumber(80),
+				AllowedRoutes: &gatewayapi.AllowedRoutes{
+					Namespaces: &gatewayapi.RouteNamespaces{
+						From: &same,
+					},
+				},
+			},
+		}
+		valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
+		Expect(valids).To(ConsistOf(
+			HaveField("Name", gatewayapi.SectionName("prod-1")),
+			HaveField("Name", gatewayapi.SectionName("prod-2")),
+		))
+		Expect(conditions).To(BeEmpty())
+	})
+	It("works with multiple listeners for same hostname:port", func() {
+		same := gatewayapi.NamespacesFromSame
+		foo := gatewayapi.Hostname("foo.com")
+		listeners := []gatewayapi.Listener{
+			{
+				Name:     gatewayapi.SectionName("prod-1"),
+				Protocol: gatewayapi.HTTPProtocolType,
+				Hostname: &foo,
+				Port:     gatewayapi.PortNumber(80),
+				AllowedRoutes: &gatewayapi.AllowedRoutes{
+					Namespaces: &gatewayapi.RouteNamespaces{
+						From: &same,
+					},
+				},
+			},
+			{
+				Name:     gatewayapi.SectionName("prod-2"),
+				Protocol: gatewayapi.HTTPProtocolType,
+				Hostname: &foo,
+				Port:     gatewayapi.PortNumber(80),
+				AllowedRoutes: &gatewayapi.AllowedRoutes{
+					Namespaces: &gatewayapi.RouteNamespaces{
+						From: &same,
+					},
+				},
+			},
+		}
+		valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
+		Expect(valids).To(BeEmpty())
+		Expect(conditions).To(HaveKey(gatewayapi.SectionName("prod-1")))
+		Expect(conditions).To(HaveKey(gatewayapi.SectionName("prod-2")))
+	})
+})
diff --git a/pkg/plugins/runtime/k8s/controllers/gatewayapi/gatewayapi_suite_test.go b/pkg/plugins/runtime/k8s/controllers/gatewayapi/gatewayapi_suite_test.go
new file mode 100644
index 000000000..a4dc38a87
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/controllers/gatewayapi/gatewayapi_suite_test.go
@@ -0,0 +1,11 @@
+package gatewayapi
+
+import (
+	"testing"
+
+	"github.com/kumahq/kuma/pkg/test"
+)
+
+func TestGatewayAPI(t *testing.T) {
+	test.RunSpecs(t, "Gateway API support")
+}
diff --git a/pkg/plugins/runtime/k8s/controllers/suite_test.go b/pkg/plugins/runtime/k8s/controllers/suite_test.go
index 28e488af8..8eb62325b 100644
--- a/pkg/plugins/runtime/k8s/controllers/suite_test.go
+++ b/pkg/plugins/runtime/k8s/controllers/suite_test.go
@@ -20,7 +20,7 @@ var testEnv *envtest.Environment
 var k8sClientScheme *runtime.Scheme
 
 func TestAPIs(t *testing.T) {
-	test.RunSpecs(t, "Namespace Controller Suite")
+	test.RunSpecs(t, "K8s Controller Suite")
 }
 
 var _ = BeforeSuite(test.Within(time.Minute, func() {