Repository: Kong/kubernetes-ingress-controller
Pull request #6310 (2024-07-10 08:36:19)

chore(tests): test Gateway with HTTPS wildcard domains
<!-- Thanks for sending a pull request! Here are some tips for you:
1. If this is your first time, read our contributor guidelines https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#the-pull-request-submit-process and developer guide https://github.com/kubernetes/community/blob/master/contributors/devel/development.md
2. If you want *faster* PR reviews, read how: https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
3. Follow the instructions for writing a release note: https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md and ensure your changes are being reflected in CHANGELOG.md for the next upcoming release
-->

**What this PR does / why we need it**:

User reported 
- https://github.com/Kong/kubernetes-ingress-controller/issues/6216

this PR introduces a test that verifies it. The problem can't be reproduced, everything works as expected. The issue raised in https://github.com/Kong/kubernetes-ingress-controller/issues/6216#issuecomment-2200377297 happens when a request is made to Kong Gateway without a specific hostname, default certificate is returned to a client. Think about this as

```sh
 curl https://test.example.com --resolve test.example.com:443:172.18.128.3      
```

that works as expected (of course assuming that the certificate is trusted) vs

```sh
 curl https://172.18.128.3
```

that leads to returning the default cert - https://github.com/Kong/kubernetes-ingress-controller/issues/6216#issuecomment-2200377297 (because there is no route in Gateway for such a URL).

<!-- Please describe why this particular PR is necessary or why you see it as a nice addition -->

**Which issue this PR fixes**:

Should close https://github.com/Kong/kubernetes-ingress-controller/issues/6216 (the comment will be added and we'll wait for the reporter's response).

<!--
Here you can add any links to issues that this PR is relevant for.
You can use Github keywords (like: closes, fixes or resolves) to auto-resolve
the linked issue(s) when this PR gets merged.

For example: fixes #<issue number>
-->

**Special notes for your reviewer**:

Introducing this test would not be possible without previous work done in https://github.com/Kong/kubernetes-ingress-controller/pull/6298 and https://github.com/Kong/kubernetes-ingress-controller/pull/6307





Patch:
diff --git a/.tools_versions.yaml b/.tools_versions.yaml
index c3a338e7b..78f3b1bd7 100644
--- a/.tools_versions.yaml
+++ b/.tools_versions.yaml
@@ -7,7 +7,7 @@ kustomize: "5.3.0"
 # renovate: datasource=github-releases depName=golangci/golangci-lint
 golangci-lint: "1.59.1"
 # renovate: datasource=github-releases depName=GoogleContainerTools/skaffold
-skaffold: "2.12.0"
+skaffold: "2.13.0"
 # renovate: datasource=github-releases depName=kubernetes-sigs/controller-runtime
 setup-envtest: "0.18.4"
 # renovate: datasource=github-releases depName=elastic/crd-ref-docs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4abd3c4f..1b07db547 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -108,6 +108,9 @@ Adding a new version? You'll need three changes:
 - Generate one entity for each attached foreign entity if a `KongCustomEntity`
   resource is attached to multiple foreign Kong entities.
   [#6280](https://github.com/Kong/kubernetes-ingress-controller/pull/6280)
+- Fixed the reference checker in checking permission of remote plugins to use
+  the correct namespace of `ReferenceGrant` required.
+  [#6295](https://github.com/Kong/kubernetes-ingress-controller/pull/6295)
 
 ## 3.2.2
 
diff --git a/go.mod b/go.mod
index ce2c02be0..ab59a336a 100644
--- a/go.mod
+++ b/go.mod
@@ -19,7 +19,7 @@ go 1.22.3
 // toolchain go1.21.0
 
 require (
-	cloud.google.com/go/container v1.37.2
+	cloud.google.com/go/container v1.37.3
 	github.com/Masterminds/sprig/v3 v3.2.3
 	github.com/avast/retry-go/v4 v4.6.0
 	github.com/blang/semver/v4 v4.0.0
@@ -49,7 +49,7 @@ require (
 	github.com/stretchr/testify v1.9.0
 	github.com/testcontainers/testcontainers-go v0.31.0
 	go.uber.org/zap v1.27.0
-	google.golang.org/api v0.187.0
+	google.golang.org/api v0.188.0
 	k8s.io/api v0.30.2
 	k8s.io/apiextensions-apiserver v0.30.2
 	k8s.io/apimachinery v0.30.2
@@ -64,7 +64,7 @@ require (
 )
 
 require (
-	cloud.google.com/go/auth v0.6.1 // indirect
+	cloud.google.com/go/auth v0.7.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.26.0 // indirect
@@ -72,7 +72,7 @@ require (
 )
 
 require (
-	cloud.google.com/go/compute/metadata v0.3.0 // indirect
+	cloud.google.com/go/compute/metadata v0.4.0 // indirect
 	dario.cat/mergo v1.0.0
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Kong/go-diff v1.2.2 // indirect
@@ -201,20 +201,20 @@ require (
 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 // indirect
-	golang.org/x/crypto v0.24.0 // indirect
+	golang.org/x/crypto v0.25.0 // indirect
 	golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
 	golang.org/x/mod v0.17.0 // indirect
-	golang.org/x/net v0.26.0 // indirect
+	golang.org/x/net v0.27.0 // indirect
 	golang.org/x/oauth2 v0.21.0 // indirect
 	golang.org/x/sync v0.7.0
-	golang.org/x/sys v0.21.0 // indirect
-	golang.org/x/term v0.21.0 // indirect
+	golang.org/x/sys v0.22.0 // indirect
+	golang.org/x/term v0.22.0 // indirect
 	golang.org/x/text v0.16.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect
 	google.golang.org/grpc v1.65.0
 	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
diff --git a/go.sum b/go.sum
index 8a33f453f..4e859eef4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,14 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
 cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
-cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38=
-cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4=
+cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
+cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
 cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
 cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
-cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
-cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
-cloud.google.com/go/container v1.37.2 h1:g5zm1SUBZ+q7IvtI5hM/6xcpf2C/bFfN2EXzS07Iz9k=
-cloud.google.com/go/container v1.37.2/go.mod h1:2ly7zpBmWtYjjuoB3fHyq8Gqrxaj2NIwzwVRpUcKYXk=
+cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c=
+cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
+cloud.google.com/go/container v1.37.3 h1:FCKTPz9OtId4LzB7dXzp+bDP4G0vlQOdUSYn2MddF38=
+cloud.google.com/go/container v1.37.3/go.mod h1:XKwtVfsTBsnZ9Ve1Pw2wkjk5kSjJqsHl3oBrbbi4w/M=
 dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
 dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
@@ -467,8 +467,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
-golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
@@ -495,8 +495,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
-golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
 golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
@@ -531,14 +531,14 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
-golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
+golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
+golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -565,17 +565,17 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
 gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
-google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
-google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
+google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
+google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc=
-google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
diff --git a/internal/dataplane/kongstate/kongstate.go b/internal/dataplane/kongstate/kongstate.go
index 598cab97f..6342f738d 100644
--- a/internal/dataplane/kongstate/kongstate.go
+++ b/internal/dataplane/kongstate/kongstate.go
@@ -19,9 +19,9 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
-	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 	kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
 )
@@ -351,18 +351,18 @@ func (ks *KongState) getPluginRelations(cacheStore store.Storer, log logr.Logger
 		RouteRelation         entityRelationType = iota
 		ServiceRelation       entityRelationType = iota
 	)
-	addRelation := func(referer client.Object, plugin annotations.NamespacedKongPlugin, identifier string, t entityRelationType) {
+	addRelation := func(referrer client.Object, plugin annotations.NamespacedKongPlugin, identifier string, t entityRelationType) {
 		// There are 2 types of KongPlugin references: local and remote.
-		// A local reference is one where the KongPlugin is in the same namespace as the referer.
+		// A local reference is one where the KongPlugin is in the same namespace as the referrer.
 		// A remote reference is one where the KongPlugin is in a different namespace.
 		// By default a KongPlugin is considered local.
 		// If the plugin has a namespace specified, it is considered remote.
 		//
-		// The referer is the entity that the KongPlugin is associated with.
+		// The referrer is the entity that the KongPlugin is associated with.
 		//
 		// Code in buildPlugins() will combine plugin associations into
 		// multi-entity plugins within the local namespace
-		namespace, err := extractReferredPluginNamespace(cacheStore, referer, plugin)
+		namespace, err := extractReferredPluginNamespace(log, cacheStore, referrer, plugin)
 		if err != nil {
 			log.Error(err, "could not bind requested plugin", "plugin", plugin.Name, "namespace", plugin.Namespace)
 			return
@@ -398,8 +398,11 @@ func (ks *KongState) getPluginRelations(cacheStore store.Storer, log logr.Logger
 			ingress := ks.Services[i].Routes[j].Ingress
 			pluginList := annotations.ExtractNamespacedKongPluginsFromAnnotations(ingress.Annotations)
 			for _, plugin := range pluginList {
-				// pretend we have a full Ingress struct for reference checks
+				// pretend we have a full Ingress struct for reference checks.
+				// REVIEW: we only need an object to carry type meta and object meta here, maybe we should create some other types of virtual object here?
 				virtualIngress := netv1.Ingress{
+					// Fill the actual type of the object for reference checks.
+					TypeMeta: util.TypeMetaFromGVK(ingress.GroupVersionKind),
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: ingress.Namespace,
 						Name:      ingress.Name,
@@ -428,38 +431,59 @@ func (ks *KongState) getPluginRelations(cacheStore store.Storer, log logr.Logger
 }
 
 type pluginReference struct {
-	Referer   client.Object
+	Referrer  client.Object
 	Namespace string
 	Name      string
 }
 
-func isRemotePluginReferenceAllowed(s store.Storer, r pluginReference) error {
+func isRemotePluginReferenceAllowed(log logr.Logger, s store.Storer, r pluginReference) error {
 	// remote plugin. considered part of this namespace if a suitable ReferenceGrant exists
 	grants, err := s.ListReferenceGrants()
 	if err != nil {
 		return fmt.Errorf("could not retrieve ReferenceGrants from store when building plugin relations map: %w", err)
 	}
-	allowed := gatewayapi.GetPermittedForReferenceGrantFrom(gatewayapi.ReferenceGrantFrom{
-		Group:     gatewayapi.Group(r.Referer.GetObjectKind().GroupVersionKind().Group),
-		Kind:      gatewayapi.Kind(r.Referer.GetObjectKind().GroupVersionKind().Kind),
-		Namespace: gatewayapi.Namespace(r.Referer.GetNamespace()),
-	}, grants)
+	allowed := gatewayapi.GetPermittedForReferenceGrantFrom(
+		log,
+		gatewayapi.ReferenceGrantFrom{
+			Group:     gatewayapi.Group(r.Referrer.GetObjectKind().GroupVersionKind().Group),
+			Kind:      gatewayapi.Kind(r.Referrer.GetObjectKind().GroupVersionKind().Kind),
+			Namespace: gatewayapi.Namespace(r.Referrer.GetNamespace()),
+		},
+		grants,
+	)
+
+	// TODO https://github.com/Kong/kubernetes-ingress-controller/issues/6000
+	// Our reference checking code wasn't originally designed to handle multiple object types and relationships and
+	// wasn't designed with much guidance for future usage. It expects something akin to the BackendRef section of an
+	// HTTPRoute or similar, since that's what it was originally designed for. A future simplified model should probably
+	// work with source and target client.Object resources derived from the particular resources' reference specs, as
+	// those reference formats aren't necessarily consistent. We should possibly push for a standard upstream utility as
+	// part of https://github.com/kubernetes/enhancements/issues/3766 if it proceeds further, as this is can be difficult
+	// to wrap your head around when deep in the code that's checking an individual use case. A standard model for "these
+	// are the inputs and outputs for a reference grant check, derive them from your spec" should help avoid inconsistency
+	// in check implementation.
 
 	// we don't have a full plugin resource here for the grant checker, so we build a fake one with the correct
-	// name and namespace
-	virtualReference := gatewayapi.PluginLabelReference{
-		Namespace: lo.ToPtr(r.Referer.GetNamespace()),
+	// name and namespace.
+	pluginReference := gatewayapi.PluginLabelReference{
+		Namespace: &r.Namespace,
 		Name:      r.Name,
 	}
-	virtualPlugin := &kongv1.KongPlugin{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: r.Namespace,
-			Name:      r.Name,
-		},
-	}
-	if !gatewayapi.NewRefCheckerForKongPlugin(virtualPlugin, virtualReference).IsRefAllowedByGrant(allowed) {
+
+	// Because we should check whether it is allowed to refer FROM the referrer TO the plugin here,
+	// we should put the referrer on the "target" and the plugin on the "backendRef".
+
+	log.V(logging.TraceLevel).Info("requested grant to plugins",
+		"from-namespace", r.Referrer.GetNamespace(),
+		"from-group", r.Referrer.GetObjectKind().GroupVersionKind().Group,
+		"from-kind", r.Referrer.GetObjectKind().GroupVersionKind().Kind,
+		"to-namespace", r.Namespace,
+		"to-name", r.Name,
+	)
+
+	if !gatewayapi.NewRefCheckerForKongPlugin(log, r.Referrer, pluginReference).IsRefAllowedByGrant(allowed) {
 		return fmt.Errorf("no grant found for %s in %s to plugin %s in %s requested remote KongPlugin bind",
-			r.Referer.GetObjectKind().GroupVersionKind().Kind, r.Referer.GetNamespace(), r.Name, r.Namespace)
+			r.Referrer.GetObjectKind().GroupVersionKind().Kind, r.Referrer.GetNamespace(), r.Name, r.Namespace)
 	}
 	return nil
 }
@@ -723,8 +747,8 @@ func (ks *KongState) getPluginRelatedEntitiesRef(cacheStore store.Storer, log lo
 		RelatedEntities:      map[string]RelatedEntitiesRef{},
 		RouteAttachedService: map[string]*Service{},
 	}
-	addRelation := func(referer client.Object, plugin annotations.NamespacedKongPlugin, entity any) {
-		namespace, err := extractReferredPluginNamespace(cacheStore, referer, plugin)
+	addRelation := func(referrer client.Object, plugin annotations.NamespacedKongPlugin, entity any) {
+		namespace, err := extractReferredPluginNamespace(log, cacheStore, referrer, plugin)
 		if err != nil {
 			log.Error(err, "could not bind requested plugin", "plugin", plugin.Name, "namespace", plugin.Namespace)
 			return
@@ -759,6 +783,8 @@ func (ks *KongState) getPluginRelatedEntitiesRef(cacheStore store.Storer, log lo
 			for _, plugin := range pluginList {
 				// Pretend we have a full Ingress struct for reference checks.
 				virtualIngress := netv1.Ingress{
+					// Fill the actual type of the object for reference checks.
+					TypeMeta: util.TypeMetaFromGVK(ingress.GroupVersionKind),
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: ingress.Namespace,
 						Name:      ingress.Name,
@@ -783,24 +809,25 @@ func (ks *KongState) getPluginRelatedEntitiesRef(cacheStore store.Storer, log lo
 }
 
 func extractReferredPluginNamespace(
-	cacheStore store.Storer, referer client.Object, plugin annotations.NamespacedKongPlugin,
+	log logr.Logger, cacheStore store.Storer, referrer client.Object, plugin annotations.NamespacedKongPlugin,
 ) (string, error) {
 	// There are 2 types of KongPlugin references: local and remote.
-	// A local reference is one where the KongPlugin is in the same namespace as the referer.
+	// A local reference is one where the KongPlugin is in the same namespace as the referrer.
 	// A remote reference is one where the KongPlugin is in a different namespace.
 	// By default a KongPlugin is considered local.
 	// If the plugin has a namespace specified, it is considered remote.
 	//
-	// The referer is the entity that the KongPlugin is associated with.
+	// The referrer is the entity that the KongPlugin is associated with.
 	if plugin.Namespace == "" {
-		return referer.GetNamespace(), nil
+		return referrer.GetNamespace(), nil
 	}
 
 	// remote KongPlugin, permitted if ReferenceGrant allows.
 	err := isRemotePluginReferenceAllowed(
+		log,
 		cacheStore,
 		pluginReference{
-			Referer:   referer,
+			Referrer:  referrer,
 			Namespace: plugin.Namespace,
 			Name:      plugin.Name,
 		},
diff --git a/internal/dataplane/translator/backendref.go b/internal/dataplane/translator/backendref.go
index 781fb9c2a..770b14c52 100644
--- a/internal/dataplane/translator/backendref.go
+++ b/internal/dataplane/translator/backendref.go
@@ -65,7 +65,7 @@ func backendRefsToKongStateBackends(
 		}
 
 		if !util.IsBackendRefGroupKindSupported(backendRef.Group, backendRef.Kind) ||
-			!gatewayapi.NewRefCheckerForRoute(route, backendRef).IsRefAllowedByGrant(allowed) {
+			!gatewayapi.NewRefCheckerForRoute(logger, route, backendRef).IsRefAllowedByGrant(allowed) {
 			// we log impermissible refs rather than failing the entire rule. while we cannot actually route to
 			// these, we do not want a single impermissible ref to take the entire rule offline. in the case of edits,
 			// failing the entire rule could potentially delete routes that were previously online and in use, and
diff --git a/internal/dataplane/translator/translate_utils.go b/internal/dataplane/translator/translate_utils.go
index 6a5449885..c9e7c8793 100644
--- a/internal/dataplane/translator/translate_utils.go
+++ b/internal/dataplane/translator/translate_utils.go
@@ -2,7 +2,6 @@ package translator
 
 import (
 	"fmt"
-	"reflect"
 
 	"github.com/go-logr/logr"
 	"github.com/kong/go-kong/kong"
@@ -42,29 +41,6 @@ func convertGatewayMatchHeadersToKongRouteMatchHeaders(headers []gatewayapi.HTTP
 	return convertedHeaders, nil
 }
 
-// GetPermittedForReferenceGrantFrom takes a ReferenceGrant From (a namespace, group, and kind) and returns a map
-// from a namespace to a slice of ReferenceGrant Tos. When a To is included in the slice, the key namespace has a
-// ReferenceGrant with those Tos and the input From.
-func GetPermittedForReferenceGrantFrom(
-	from gatewayapi.ReferenceGrantFrom,
-	grants []*gatewayapi.ReferenceGrant,
-) map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo {
-	allowed := make(map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo)
-	// loop over all From values in all grants. if we find a match, add all Tos to the list of Tos allowed for the
-	// grant namespace. this technically could add duplicate copies of the Tos if there are duplicate Froms (it makes
-	// no sense to add them, but it's allowed), but duplicate Tos are harmless (we only care about having at least one
-	// matching To when checking if a ReferenceGrant allows a reference)
-	for _, grant := range grants {
-		for _, otherFrom := range grant.Spec.From {
-			if reflect.DeepEqual(from, otherFrom) {
-				allowed[gatewayapi.Namespace(grant.ObjectMeta.Namespace)] = append(allowed[gatewayapi.Namespace(grant.ObjectMeta.Namespace)], grant.Spec.To...)
-			}
-		}
-	}
-
-	return allowed
-}
-
 // generateKongServiceFromBackendRefWithName translates backendRefs into a Kong service for use with the
 // rules generated from a Gateway APIs route. The service name is provided by the caller.
 func generateKongServiceFromBackendRefWithName(
@@ -82,11 +58,15 @@ func generateKongServiceFromBackendRefWithName(
 	if err != nil {
 		return kongstate.Service{}, fmt.Errorf("could not retrieve ReferenceGrants for %s: %w", objName, err)
 	}
-	allowed := GetPermittedForReferenceGrantFrom(gatewayapi.ReferenceGrantFrom{
-		Group:     gatewayapi.Group(route.GetObjectKind().GroupVersionKind().Group),
-		Kind:      gatewayapi.Kind(route.GetObjectKind().GroupVersionKind().Kind),
-		Namespace: gatewayapi.Namespace(route.GetNamespace()),
-	}, grants)
+	allowed := gatewayapi.GetPermittedForReferenceGrantFrom(
+		logger,
+		gatewayapi.ReferenceGrantFrom{
+			Group:     gatewayapi.Group(route.GetObjectKind().GroupVersionKind().Group),
+			Kind:      gatewayapi.Kind(route.GetObjectKind().GroupVersionKind().Kind),
+			Namespace: gatewayapi.Namespace(route.GetNamespace()),
+		},
+		grants,
+	)
 
 	backends := backendRefsToKongStateBackends(logger, storer, route, backendRefs, allowed)
 
diff --git a/internal/gatewayapi/references.go b/internal/gatewayapi/references.go
index c97112739..ae331f771 100644
--- a/internal/gatewayapi/references.go
+++ b/internal/gatewayapi/references.go
@@ -1,9 +1,13 @@
 package gatewayapi
 
 import (
+	"fmt"
 	"reflect"
 
+	"github.com/go-logr/logr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 // RefChecker is a wrapper type that facilitates checking whether a backendRef is allowed
@@ -11,20 +15,23 @@ import (
 type RefChecker[T BackendRefT] struct {
 	target     client.Object
 	backendRef T
+	log        logr.Logger
 }
 
 // NewRefCheckerForRoute returns a RefChecker for the provided route and backendRef.
-func NewRefCheckerForRoute[T BackendRefT](route client.Object, ref T) RefChecker[T] {
+func NewRefCheckerForRoute[T BackendRefT](log logr.Logger, route client.Object, ref T) RefChecker[T] {
 	return RefChecker[T]{
 		target:     route,
 		backendRef: ref,
+		log:        log.WithName("refchecker"),
 	}
 }
 
-func NewRefCheckerForKongPlugin[T BackendRefT](target client.Object, requester T) RefChecker[T] {
+func NewRefCheckerForKongPlugin[T BackendRefT](log logr.Logger, target client.Object, requester T) RefChecker[T] {
 	return RefChecker[T]{
 		target:     target,
 		backendRef: requester,
+		log:        log.WithName("refchecker"),
 	}
 }
 
@@ -55,7 +62,9 @@ func (rc RefChecker[T]) IsRefAllowedByGrant(
 			return true
 		}
 
+		rc.log.V(logging.TraceLevel).Info("checking reference for BackendRef")
 		return isRefAllowedByGrant(
+			rc.log,
 			(*string)(br.Namespace),
 			(string)(br.Name),
 			(string)(*br.Group),
@@ -68,7 +77,9 @@ func (rc RefChecker[T]) IsRefAllowedByGrant(
 			return true
 		}
 
+		rc.log.V(logging.TraceLevel).Info("checking reference to Secret")
 		return isRefAllowedByGrant(
+			rc.log,
 			(*string)(br.Namespace),
 			(string)(br.Name),
 			(string)(*br.Group),
@@ -77,26 +88,58 @@ func (rc RefChecker[T]) IsRefAllowedByGrant(
 		)
 
 	case PluginLabelReference:
+		rc.log.V(logging.TraceLevel).Info("checking reference to KongPlugin")
 		if br.Namespace == nil {
 			return true
 		}
 
 		return isRefAllowedByGrant(
+			rc.log,
 			(br.Namespace),
 			(br.Name),
 			"configuration.konghq.com", // TODO https://github.com/Kong/kubernetes-ingress-controller/issues/6000
 			"KongPlugin",               // TODO These magic strings should become unnecessary once we work with client.Object
 			allowedRefs,
 		)
-	}
 
+		// TODO this is somewhat like the desired end state of issue #6000, but isn't viable at the moment because we
+		// don't actually have the From object here, we have the reference that describes it. the assertion here always
+		// fails because we've already extracted a type string into "br" from the reference. were we constructing an
+		// object from the reference that'd work, but refactoring that without a bunch of cases to build the object
+		// isn't obvious.
+
+		// default:
+		//	if obj, ok := br.(client.Object); ok {
+		//		if obj.GetNamespace() == "" {
+		//			return true
+		//		}
+		//		rc.log.V(logging.TraceLevel).Info(fmt.Sprintf("checking reference from client.Object (actual type %t", br))
+
+		//		return isRefAllowedByGrant(
+		//			rc.log,
+		//			lo.ToPtr(obj.GetNamespace()),
+		//			obj.GetName(),
+		//			obj.GetObjectKind().GroupVersionKind().Group,
+		//			obj.GetObjectKind().GroupVersionKind().Kind,
+		//			allowedRefs,
+		//		)
+		//	} else {
+		//		rc.log.V(logging.TraceLevel).Info(fmt.Sprintf("could not check reference for non-client.Object (actual type %t)", br))
+		//	}
+
+	}
 	return false
 }
 
+// TODO https://github.com/Kong/kubernetes-ingress-controller/issues/6000
+// this does not indicate the relationship between the NN+GK args and the allowed arg, which makes it rather
+// difficult to understand
+
 // isRefAllowedByGrant checks if backendRef is permitted by the provided namespace-indexed ReferenceGrantTo set: allowed.
 // allowed is assumed to contain Tos that only match the backendRef's parent's From, as returned by
 // GetPermittedForReferenceGrantFrom.
 func isRefAllowedByGrant(
+	log logr.Logger,
 	namespace *string,
 	name string,
 	group string,
@@ -107,19 +150,40 @@ func isRefAllowedByGrant(
 		// local references are always fine
 		return true
 	}
-	for _, to := range allowed[Namespace(*namespace)] {
+	scoped := log.WithValues(
+		"namespace", *namespace,
+		"requested-group", group,
+		"requested-kind", kind,
+		"requested-name", name,
+	)
+	scoped.V(logging.TraceLevel).Info(fmt.Sprintf("checking %d entries for namespace", len(allowed[Namespace(*namespace)])))
+	for i, to := range allowed[Namespace(*namespace)] {
+		toName := ""
+		if to.Name != nil {
+			toName = string(*to.Name)
+		}
+		scoped = scoped.WithValues(
+			"to-group", to.Group,
+			"to-kind", to.Kind,
+			"to-name", toName,
+			"to-index", i,
+		)
 		if string(to.Group) == group && string(to.Kind) == kind {
 			if to.Name != nil {
 				if string(*to.Name) == name {
+					scoped.V(logging.TraceLevel).Info("requested ref allowed by grant")
 					return true
 				}
 			} else {
 				// if no referent name specified, matching group/kind is sufficient
+				scoped.V(logging.TraceLevel).Info("requested ref allowed by grant To")
 				return true
 			}
 		}
+		scoped.V(logging.TraceLevel).Info("grant To did not match requested ref target")
 	}
 
+	scoped.V(logging.TraceLevel).Info("no grants matching requested ref target")
 	return false
 }
 
@@ -127,6 +191,7 @@ func isRefAllowedByGrant(
 // from a namespace to a slice of ReferenceGrant Tos. When a To is included in the slice, the key namespace has a
 // ReferenceGrant with those Tos and the input From.
 func GetPermittedForReferenceGrantFrom(
+	log logr.Logger,
 	from ReferenceGrantFrom,
 	grants []*ReferenceGrant,
 ) map[Namespace][]ReferenceGrantTo {
@@ -135,10 +200,44 @@ func GetPermittedForReferenceGrantFrom(
 	// grant namespace. this technically could add duplicate copies of the Tos if there are duplicate Froms (it makes
 	// no sense to add them, but it's allowed), but duplicate Tos are harmless (we only care about having at least one
 	// matching To when checking if a ReferenceGrant allows a reference)
+	scoped := log.WithName("refchecker")
 	for _, grant := range grants {
 		for _, otherFrom := range grant.Spec.From {
 			if reflect.DeepEqual(from, otherFrom) {
+				scoped.V(logging.TraceLevel).Info("grant from equal, adding to allowed",
+					"grant-namespace", grant.Namespace,
+					"grant-name", grant.Name,
+					"grant-from-namespace", otherFrom.Namespace,
+					"grant-from-group", otherFrom.Group,
+					"grant-from-kind", otherFrom.Kind,
+					"requested-from-namespace", from.Namespace,
+					"requested-from-group", from.Group,
+					"requested-from-kind", from.Kind,
+				)
 				allowed[Namespace(grant.ObjectMeta.Namespace)] = append(allowed[Namespace(grant.ObjectMeta.Namespace)], grant.Spec.To...)
+				for _, to := range grant.Spec.To {
+					name := ""
+					if to.Name != nil {
+						name = string(*to.Name)
+					}
+					scoped.V(logging.TraceLevel).Info("added ReferenceGrantTo to namespace allowed list",
+						"namespace", grant.ObjectMeta.Namespace,
+						"to-group", to.Group,
+						"to-kind", to.Kind,
+						"to-name", name,
+					)
+				}
+			} else {
+				scoped.V(logging.TraceLevel).Info("grant from not equal, excluding from allowed",
+					"grant-namespace", grant.Namespace,
+					"grant-name", grant.Name,
+					"grant-from-namespace", otherFrom.Namespace,
+					"grant-from-group", otherFrom.Group,
+					"grant-from-kind", otherFrom.Kind,
+					"requested-from-namespace", from.Namespace,
+					"requested-from-group", from.Group,
+					"requested-from-kind", from.Kind,
+				)
 			}
 		}
 	}
diff --git a/internal/util/objectmeta.go b/internal/util/objectmeta.go
index 333348d7b..9a763242e 100644
--- a/internal/util/objectmeta.go
+++ b/internal/util/objectmeta.go
@@ -1,6 +1,7 @@
 package util
 
 import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -24,3 +25,16 @@ func FromK8sObject(obj client.Object) K8sObjectInfo {
 		GroupVersionKind: obj.GetObjectKind().GroupVersionKind(),
 	}
 }
+
+// TypeMetaFromGVK returns typemeta from groupversionkind of a k8s object.
+func TypeMetaFromGVK(gvk schema.GroupVersionKind) metav1.TypeMeta {
+	return metav1.TypeMeta{
+		APIVersion: gvk.GroupVersion().String(),
+		Kind:       gvk.Kind,
+	}
+}
+
+// TypeMetaFromK8sObject returns the typemeta of a client Object by its GVK.
+func TypeMetaFromK8sObject(obj client.Object) metav1.TypeMeta {
+	return TypeMetaFromGVK(obj.GetObjectKind().GroupVersionKind())
+}


Test patch:
diff --git a/.github/test_dependencies.yaml b/.github/test_dependencies.yaml
index 98edd796a..782d76efa 100644
--- a/.github/test_dependencies.yaml
+++ b/.github/test_dependencies.yaml
@@ -50,14 +50,14 @@ integration:
   # renovate: datasource=docker depName=kong versioning=docker
   kong-oss: '3.7.1'
   # renovate: datasource=docker depName=kong/kong-gateway versioning=docker
-  kong-ee: '3.7.1.1'
+  kong-ee: '3.7.1.2'
 
 kongintegration:
   # renovate: datasource=docker depName=kong versioning=docker
   kong-oss: '3.7.1'
   # renovate: datasource=docker depName=kong/kong-gateway versioning=docker
-  kong-ee: '3.7.1.1'
+  kong-ee: '3.7.1.2'
 
 envtests:
   # renovate: datasource=docker depName=kong/kong-gateway versioning=docker
-  kong-ee: '3.7.1.1'
+  kong-ee: '3.7.1.2'
diff --git a/internal/dataplane/kongstate/kongstate_test.go b/internal/dataplane/kongstate/kongstate_test.go
index f6d5c190c..fd243daf2 100644
--- a/internal/dataplane/kongstate/kongstate_test.go
+++ b/internal/dataplane/kongstate/kongstate_test.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/labels"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
@@ -1526,3 +1527,121 @@ func (s *fakeSchemaGetter) Get(_ context.Context, entityType string) (kong.Schem
 	}
 	return schema, nil
 }
+
+func TestIsRemotePluginReferenceAllowed(t *testing.T) {
+	serviceTypeMeta := metav1.TypeMeta{
+		Kind: "Service",
+	}
+
+	testCases := []struct {
+		name            string
+		referrer        client.Object
+		pluginNamespace string
+		pluginName      string
+		referenceGrants []*gatewayapi.ReferenceGrant
+		shouldAllow     bool
+	}{
+		{
+			name: "no reference grant",
+			referrer: &corev1.Service{
+				TypeMeta: serviceTypeMeta,
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "foo",
+					Name:      "service-foo",
+				},
+			},
+			pluginNamespace: "bar",
+			pluginName:      "plugin-bar",
+			shouldAllow:     false,
+		},
+		{
+			name: "have reference grant",
+			referrer: &corev1.Service{
+				TypeMeta: serviceTypeMeta,
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "foo",
+					Name:      "service-foo",
+				},
+			},
+			pluginNamespace: "bar",
+			pluginName:      "plugin-bar",
+			referenceGrants: []*gatewayapi.ReferenceGrant{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "bar", // same namespace as plugin
+						Name:      "grant-1",
+					},
+					Spec: gatewayapi.ReferenceGrantSpec{
+						From: []gatewayapi.ReferenceGrantFrom{
+							{
+								Kind:      gatewayapi.Kind("Service"),
+								Namespace: "foo",
+							},
+						},
+						To: []gatewayapi.ReferenceGrantTo{
+							{
+								Group: gatewayapi.Group(kongv1.GroupVersion.Group),
+								Kind:  gatewayapi.Kind("KongPlugin"),
+							},
+						},
+					},
+				},
+			},
+			shouldAllow: true,
+		},
+		{
+			name: "reference grant created but in different namespace",
+			referrer: &corev1.Service{
+				TypeMeta: serviceTypeMeta,
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "foo",
+					Name:      "service-foo",
+				},
+			},
+			pluginNamespace: "bar",
+			pluginName:      "plugin-bar",
+			referenceGrants: []*gatewayapi.ReferenceGrant{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "foo", // Not same namespace as plugin
+						Name:      "grant-1",
+					},
+					Spec: gatewayapi.ReferenceGrantSpec{
+						From: []gatewayapi.ReferenceGrantFrom{
+							{
+								Kind:      gatewayapi.Kind("Service"),
+								Namespace: "foo",
+							},
+						},
+						To: []gatewayapi.ReferenceGrantTo{
+							{
+								Group: gatewayapi.Group(kongv1.GroupVersion.Group),
+								Kind:  gatewayapi.Kind("KongPlugin"),
+							},
+						},
+					},
+				},
+			},
+			shouldAllow: false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			s, err := store.NewFakeStore(store.FakeObjects{
+				ReferenceGrants: tc.referenceGrants,
+			})
+			require.NoError(t, err)
+			err = isRemotePluginReferenceAllowed(logr.Discard(), s, pluginReference{
+				Referrer:  tc.referrer,
+				Namespace: tc.pluginNamespace,
+				Name:      tc.pluginName,
+			})
+			if tc.shouldAllow {
+				require.NoError(t, err)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
diff --git a/internal/dataplane/translator/translate_utils_test.go b/internal/dataplane/translator/translate_utils_test.go
index 9bfb62425..97c6d8cf3 100644
--- a/internal/dataplane/translator/translate_utils_test.go
+++ b/internal/dataplane/translator/translate_utils_test.go
@@ -108,154 +108,6 @@ func TestConvertGatewayMatchHeadersToKongRouteMatchHeaders(t *testing.T) {
 	}
 }
 
-func TestGetPermittedForReferenceGrantFrom(t *testing.T) {
-	grants := []*gatewayapi.ReferenceGrant{
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      uuid.NewString(),
-				Namespace: "fitrat",
-			},
-			Spec: gatewayapi.ReferenceGrantSpec{
-				From: []gatewayapi.ReferenceGrantFrom{
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("garbage"),
-					},
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("behbudiy"),
-					},
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("qodiriy"),
-					},
-				},
-				To: []gatewayapi.ReferenceGrantTo{
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantOne"),
-					},
-				},
-			},
-		},
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      uuid.NewString(),
-				Namespace: "cholpon",
-			},
-			Spec: gatewayapi.ReferenceGrantSpec{
-				From: []gatewayapi.ReferenceGrantFrom{
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("UDPRoute"),
-						Namespace: gatewayapi.Namespace("behbudiy"),
-					},
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("qodiriy"),
-					},
-				},
-				To: []gatewayapi.ReferenceGrantTo{
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantTwo"),
-					},
-				},
-			},
-		},
-	}
-	tests := []struct {
-		msg    string
-		from   gatewayapi.ReferenceGrantFrom
-		result map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo
-	}{
-		{
-			msg: "no matches whatsoever",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("invalid.example"),
-				Kind:      gatewayapi.Kind("invalid"),
-				Namespace: gatewayapi.Namespace("invalid"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "non-matching namespace",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("UDPRoute"),
-				Namespace: gatewayapi.Namespace("niyazi"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "non-matching kind",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("TLSRoute"),
-				Namespace: gatewayapi.Namespace("behbudiy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "non-matching group",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("invalid.example"),
-				Kind:      gatewayapi.Kind("UDPRoute"),
-				Namespace: gatewayapi.Namespace("behbudiy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "single match",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("UDPRoute"),
-				Namespace: gatewayapi.Namespace("behbudiy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{
-				"cholpon": {
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantTwo"),
-					},
-				},
-			},
-		},
-		{
-			msg: "multiple matches",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("TCPRoute"),
-				Namespace: gatewayapi.Namespace("qodiriy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{
-				"cholpon": {
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantTwo"),
-					},
-				},
-				"fitrat": {
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantOne"),
-					},
-				},
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.msg, func(t *testing.T) {
-			result := GetPermittedForReferenceGrantFrom(tt.from, grants)
-			assert.Equal(t, tt.result, result)
-		})
-	}
-}
-
 func TestGenerateKongServiceFromBackendRef(t *testing.T) {
 	grants := []*gatewayapi.ReferenceGrant{
 		{
diff --git a/internal/dataplane/translator/wrappers_refchecker_test.go b/internal/dataplane/translator/wrappers_refchecker_test.go
index 9a18525c5..f92300066 100644
--- a/internal/dataplane/translator/wrappers_refchecker_test.go
+++ b/internal/dataplane/translator/wrappers_refchecker_test.go
@@ -3,6 +3,7 @@ package translator
 import (
 	"testing"
 
+	"github.com/go-logr/logr"
 	"github.com/samber/lo"
 	"github.com/stretchr/testify/require"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -111,7 +112,7 @@ func TestRefChecker_IsRefAllowedByGrant(t *testing.T) {
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
-			rc := gatewayapi.NewRefCheckerForRoute(tc.route, tc.backendRef)
+			rc := gatewayapi.NewRefCheckerForRoute(logr.Discard(), tc.route, tc.backendRef)
 			result := rc.IsRefAllowedByGrant(tc.allowedRefs)
 			require.Equal(t, tc.expected, result)
 		})
diff --git a/internal/gatewayapi/references_test.go b/internal/gatewayapi/references_test.go
new file mode 100644
index 000000000..1f9b32fb3
--- /dev/null
+++ b/internal/gatewayapi/references_test.go
@@ -0,0 +1,158 @@
+package gatewayapi
+
+import (
+	"testing"
+
+	"github.com/go-logr/logr"
+	"github.com/google/uuid"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestGetPermittedForReferenceGrantFrom(t *testing.T) {
+	grants := []*ReferenceGrant{
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      uuid.NewString(),
+				Namespace: "fitrat",
+			},
+			Spec: ReferenceGrantSpec{
+				From: []ReferenceGrantFrom{
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("garbage"),
+					},
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("behbudiy"),
+					},
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("qodiriy"),
+					},
+				},
+				To: []ReferenceGrantTo{
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantOne"),
+					},
+				},
+			},
+		},
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      uuid.NewString(),
+				Namespace: "cholpon",
+			},
+			Spec: ReferenceGrantSpec{
+				From: []ReferenceGrantFrom{
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("UDPRoute"),
+						Namespace: Namespace("behbudiy"),
+					},
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("qodiriy"),
+					},
+				},
+				To: []ReferenceGrantTo{
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantTwo"),
+					},
+				},
+			},
+		},
+	}
+	tests := []struct {
+		msg    string
+		from   ReferenceGrantFrom
+		result map[Namespace][]ReferenceGrantTo
+	}{
+		{
+			msg: "no matches whatsoever",
+			from: ReferenceGrantFrom{
+				Group:     Group("invalid.example"),
+				Kind:      Kind("invalid"),
+				Namespace: Namespace("invalid"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "non-matching namespace",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("UDPRoute"),
+				Namespace: Namespace("niyazi"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "non-matching kind",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("TLSRoute"),
+				Namespace: Namespace("behbudiy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "non-matching group",
+			from: ReferenceGrantFrom{
+				Group:     Group("invalid.example"),
+				Kind:      Kind("UDPRoute"),
+				Namespace: Namespace("behbudiy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "single match",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("UDPRoute"),
+				Namespace: Namespace("behbudiy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{
+				"cholpon": {
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantTwo"),
+					},
+				},
+			},
+		},
+		{
+			msg: "multiple matches",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("TCPRoute"),
+				Namespace: Namespace("qodiriy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{
+				"cholpon": {
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantTwo"),
+					},
+				},
+				"fitrat": {
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantOne"),
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.msg, func(t *testing.T) {
+			result := GetPermittedForReferenceGrantFrom(logr.Discard(), tt.from, grants)
+			assert.Equal(t, tt.result, result)
+		})
+	}
+}
diff --git a/internal/util/objectmeta_test.go b/internal/util/objectmeta_test.go
index 2694382ca..cf1f76b7b 100644
--- a/internal/util/objectmeta_test.go
+++ b/internal/util/objectmeta_test.go
@@ -4,6 +4,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
 	netv1 "k8s.io/api/networking/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -94,3 +96,53 @@ func BenchmarkFromK8sObject(b *testing.B) {
 		_ = out
 	}
 }
+
+func TestTypeMetaFromK8sObject(t *testing.T) {
+	testCases := []struct {
+		name     string
+		obj      client.Object
+		typeMeta metav1.TypeMeta
+	}{
+		{
+			name: "empty group",
+			obj: &corev1.Service{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: "v1",
+					Kind:       "Service",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc",
+					Namespace: "default",
+				},
+			},
+			typeMeta: metav1.TypeMeta{
+				APIVersion: "v1",
+				Kind:       "Service",
+			},
+		},
+		{
+			name: "non-empty group",
+			obj: &netv1.Ingress{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: "networking.k8s.io/v1",
+					Kind:       "Ingress",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "ing",
+					Namespace: "default",
+				},
+			},
+			typeMeta: metav1.TypeMeta{
+				APIVersion: "networking.k8s.io/v1",
+				Kind:       "Ingress",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			typeMeta := TypeMetaFromK8sObject(tc.obj)
+			require.Equal(t, tc.typeMeta, typeMeta)
+		})
+	}
+}
diff --git a/test/integration/isolated/ctx.go b/test/integration/isolated/ctx.go
index 70739438e..a3e53eaee 100644
--- a/test/integration/isolated/ctx.go
+++ b/test/integration/isolated/ctx.go
@@ -4,6 +4,7 @@ package isolated
 
 import (
 	"context"
+	"crypto/x509"
 	"net/url"
 	"strings"
 	"testing"
@@ -202,3 +203,19 @@ func GetIngressClassFromCtx(ctx context.Context) string {
 	}
 	return r.(string)
 }
+
+type _certPool struct{}
+
+// SetCertPoolInCtx sets the cert pool in the context.
+func SetCertPoolInCtx(ctx context.Context, certPool *x509.CertPool) context.Context {
+	return setInCtx(ctx, _certPool{}, certPool)
+}
+
+// GetCertPoolFromCtx gets the cert pool from the context.
+func GetCertPoolFromCtx(ctx context.Context) *x509.CertPool {
+	cp := ctx.Value(_certPool{})
+	if cp == nil {
+		return nil
+	}
+	return cp.(*x509.CertPool)
+}
diff --git a/test/integration/isolated/gateway_https_test.go b/test/integration/isolated/gateway_https_test.go
new file mode 100644
index 000000000..950099ae6
--- /dev/null
+++ b/test/integration/isolated/gateway_https_test.go
@@ -0,0 +1,216 @@
+//go:build integration_tests
+
+package isolated
+
+import (
+	"context"
+	"crypto/x509"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/google/uuid"
+	"github.com/kong/kubernetes-testing-framework/pkg/clusters"
+	"github.com/kong/kubernetes-testing-framework/pkg/utils/kubernetes/generators"
+	"github.com/samber/lo"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8stypes "k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/e2e-framework/pkg/envconf"
+	"sigs.k8s.io/e2e-framework/pkg/features"
+	gatewayclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
+
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/test"
+	"github.com/kong/kubernetes-ingress-controller/v3/test/helpers/certificate"
+	"github.com/kong/kubernetes-ingress-controller/v3/test/integration/consts"
+	"github.com/kong/kubernetes-ingress-controller/v3/test/internal/helpers"
+	"github.com/kong/kubernetes-ingress-controller/v3/test/internal/testlabels"
+)
+
+func TestGatewayHTTPSMultipleCertificates(t *testing.T) {
+	const (
+		wildcardExample     = "*.example.com"
+		nameWildcardExample = "example"
+		testExampleURL      = "https://test.example.com"
+
+		wildCardOneInternalExample     = "*.one.internal.example.com"
+		nameWildcardOneInternalExample = "one-internal-example"
+		testOneInternalExampleURL      = "https://test.one.internal.example.com"
+	)
+
+	f := features.
+		New("essentials").
+		WithLabel(testlabels.NetworkingFamily, testlabels.NetworkingFamilyGatewayAPI).
+		WithLabel(testlabels.Kind, testlabels.KindHTTPRoute).
+		Setup(SkipIfRouterNotExpressions).
+		WithSetup("deploy kong addon into cluster", featureSetup(
+			withControllerManagerOpts(helpers.ControllerManagerOptAdditionalWatchNamespace("default")),
+			withKongProxyEnvVars(map[string]string{
+				"PROXY_LISTEN": `0.0.0.0:8443 http2 ssl`, // Ensure that only HTTPS is available.
+			}),
+		)).
+		Assess(
+			"deploying gateway with configuration (certificates and routing) to cluster",
+			func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context {
+				cleaner := GetFromCtxForT[*clusters.Cleaner](ctx, t)
+				cluster := GetClusterFromCtx(ctx)
+				namespace := GetNamespaceForT(ctx, t)
+
+				certPool := x509.NewCertPool()
+				ctx = SetCertPoolInCtx(ctx, certPool)
+				t.Log("deploying secrets with certificates")
+				createK8sCertSecret := func(
+					ctx context.Context, t *testing.T, cluster clusters.Cluster, nn k8stypes.NamespacedName, certPool *x509.CertPool, domainName string,
+				) {
+					t.Helper()
+					certificateCrt, firstCertificateKey := certificate.MustGenerateSelfSignedCertPEMFormat(
+						certificate.WithCommonName(domainName),
+						certificate.WithDNSNames(domainName),
+					)
+					certificateSecret := &corev1.Secret{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: nn.Name,
+						},
+						Type: corev1.SecretTypeTLS,
+						Data: map[string][]byte{
+							"tls.crt": certificateCrt,
+							"tls.key": firstCertificateKey,
+						},
+					}
+					_, err := cluster.Client().CoreV1().Secrets(nn.Namespace).Create(ctx, certificateSecret, metav1.CreateOptions{})
+					require.NoError(t, err)
+					require.True(t, certPool.AppendCertsFromPEM(certificateCrt))
+				}
+				createK8sCertSecret(
+					ctx, t, cluster, k8stypes.NamespacedName{Namespace: namespace, Name: nameWildcardExample}, certPool, wildcardExample,
+				)
+				createK8sCertSecret(
+					ctx, t, cluster, k8stypes.NamespacedName{Namespace: namespace, Name: nameWildcardOneInternalExample}, certPool, wildCardOneInternalExample,
+				)
+
+				t.Log("getting a gateway client")
+				gatewayClient, err := gatewayclient.NewForConfig(cluster.Config())
+				require.NoError(t, err)
+
+				t.Log("deploying a new gatewayClass")
+				gatewayClassName := uuid.NewString()
+				gwc, err := helpers.DeployGatewayClass(ctx, gatewayClient, gatewayClassName)
+				require.NoError(t, err)
+				cleaner.Add(gwc)
+
+				t.Log("deploying a new gateway")
+				createHTTPSListener := func(t *testing.T, domainName string, certSecretName string) gatewayapi.Listener {
+					t.Helper()
+					return gatewayapi.Listener{
+						Name:     gatewayapi.SectionName(fmt.Sprintf("https-%s", certSecretName)),
+						Protocol: gatewayapi.HTTPSProtocolType,
+						Port:     gatewayapi.PortNumber(443),
+						Hostname: lo.ToPtr(gatewayapi.Hostname(domainName)),
+						TLS: &gatewayapi.GatewayTLSConfig{
+							Mode: lo.ToPtr(gatewayapi.TLSModeTerminate),
+							CertificateRefs: []gatewayapi.SecretObjectReference{
+								{
+									Name: gatewayapi.ObjectName(certSecretName),
+									Kind: lo.ToPtr(gatewayapi.Kind("Secret")),
+								},
+							},
+						},
+					}
+				}
+				gatewayName := uuid.NewString()
+				gateway, err := helpers.DeployGateway(ctx, gatewayClient, namespace, gatewayClassName, func(gw *gatewayapi.Gateway) {
+					gw.Name = gatewayName
+					gw.Spec.Listeners = []gatewayapi.Listener{
+						createHTTPSListener(t, wildcardExample, nameWildcardExample),
+						createHTTPSListener(t, wildCardOneInternalExample, nameWildcardOneInternalExample),
+					}
+				})
+				require.NoError(t, err)
+				cleaner.Add(gateway)
+
+				t.Log("deploying a minimal HTTP container")
+				deployment := generators.NewDeploymentForContainer(
+					generators.NewContainer("echo", test.EchoImage, test.EchoHTTPPort),
+				)
+				deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
+					Name:  "POD_NAME",
+					Value: "echo",
+				})
+				deployment, err = cluster.Client().AppsV1().Deployments(namespace).Create(ctx, deployment, metav1.CreateOptions{})
+				require.NoError(t, err)
+
+				t.Logf("exposing deployment %q via service", deployment.Name)
+				service := generators.NewServiceForDeployment(deployment, corev1.ServiceTypeClusterIP)
+				_, err = cluster.Client().CoreV1().Services(namespace).Create(ctx, service, metav1.CreateOptions{})
+				require.NoError(t, err)
+
+				t.Logf("creating a HTTPRoute to access deployment %q via Kong", deployment.Name)
+				httpRoute := &gatewayapi.HTTPRoute{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "echo-httproute",
+					},
+					Spec: gatewayapi.HTTPRouteSpec{
+						CommonRouteSpec: gatewayapi.CommonRouteSpec{
+							ParentRefs: []gatewayapi.ParentReference{{
+								Name: gatewayapi.ObjectName(gateway.Name),
+							}},
+						},
+						Hostnames: []gatewayapi.Hostname{
+							gatewayapi.Hostname(wildcardExample),
+							gatewayapi.Hostname(wildCardOneInternalExample),
+						},
+						Rules: []gatewayapi.HTTPRouteRule{
+							{
+								BackendRefs: []gatewayapi.HTTPBackendRef{
+									{
+										BackendRef: gatewayapi.BackendRef{
+											BackendObjectReference: gatewayapi.BackendObjectReference{
+												Name: gatewayapi.ObjectName(service.Name),
+												Port: lo.ToPtr(gatewayapi.PortNumber(test.EchoHTTPPort)),
+												Kind: lo.ToPtr(gatewayapi.Kind("Service")),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				}
+				_, err = gatewayClient.GatewayV1().HTTPRoutes(namespace).Create(ctx, httpRoute, metav1.CreateOptions{})
+				require.NoError(t, err)
+
+				return ctx
+			},
+		).
+		Assess(
+			"verifying that certs match and HTTPS traffic is routed to the service",
+			func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context {
+				verifyEventuallyGet := func(url string) {
+					helpers.EventuallyGETPath(
+						t,
+						GetHTTPSURLFromCtx(ctx),
+						url,
+						"",
+						GetCertPoolFromCtx(ctx),
+						http.StatusOK,
+						"echo",
+						nil,
+						consts.IngressWait,
+						consts.WaitTick,
+					)
+				}
+
+				t.Logf("verifying that %s is routed by %s", testExampleURL, wildcardExample)
+				verifyEventuallyGet(testExampleURL)
+				t.Logf("verifying that %s is routed by %s", testOneInternalExampleURL, wildCardOneInternalExample)
+				verifyEventuallyGet(testOneInternalExampleURL)
+
+				return ctx
+			},
+		).
+		Teardown(featureTeardown())
+
+	tenv.Test(t, f.Feature())
+}