Repository: kgateway-dev/kgateway
Pull request #10920:  [waypoint] Support AuthorizationPolicy
Author: login: PetrMc
Created: 2025-03-26T04:33:43Z | Merged: 2025-03-28T00:50:04Z

# Description

this is based on the issue https://github.com/kgateway-dev/kgateway/issues/10807

## API changes

No changes.

## Code changes

added Authorization Policy informer and logic to convert the policies to Envoy filter.

## CI changes

no changes

## Docs changes

Updated example readme for Waypoint with some minor adjustments.
TODO - Document Authorization policies example.

# Context

As waypoint is part of Istio setup - it needs to apply Authz rulles to the waypoint traffic based on Istio definitions.

## Interesting decisions

This first impletemntation that has plenty of room for improvements.

## Testing steps

Running binary in kind container on my machine.

## Notes for reviewers

Please understand it's my first contribution to the code base and obviously it's far from ideal.

# Checklist:

- [X] I have performed a self-review of my own code
- [X] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [X] I have added tests that prove my fix is effective or that my feature works




Diff:
diff --git a/api/v1alpha1/doc.go b/api/v1alpha1/doc.go
index 074aff93e61..354ba02c4e7 100644
--- a/api/v1alpha1/doc.go
+++ b/api/v1alpha1/doc.go
@@ -31,3 +31,4 @@ package v1alpha1
 // +kubebuilder:rbac:groups=networking.istio.io,resources=destinationrules,verbs=get;list;watch
 // +kubebuilder:rbac:groups=networking.istio.io,resources=serviceentries,verbs=get;list;watch
 // +kubebuilder:rbac:groups=networking.istio.io,resources=workloadentries,verbs=get;list;watch
+// +kubebuilder:rbac:groups=security.istio.io,resources=authorizationpolicies,verbs=get;list;watch
diff --git a/examples/waypoint/README.md b/examples/waypoint/README.md
index 8f7e0ddb014..faa6c0601f5 100644
--- a/examples/waypoint/README.md
+++ b/examples/waypoint/README.md
@@ -16,13 +16,13 @@ own waypoint proxy implementation, and kgateway provides a drop-in replacement.
 
 1. Deploy the test namespace
 
-```
+```bash
 kubectl apply -f examples/waypoint/httpbin-mesh.yaml
 ```
 
 This namespace has two important labels:
 
-```
+```bash
 # ztunnel will capture traffic for all services in this namespace
 istio.io/dataplane-mode: ambient
 
@@ -32,7 +32,7 @@ istio.io/use-waypoint: httpbin-waypoint
 
 2. Deploy the `kgateway-waypoint` Gateway resource
 
-```
+```bash
 kubectl apply -f examples/waypoint/waypoint-gw.yaml
 ```
 
@@ -41,8 +41,8 @@ east-west traffic for your mesh.
 
 3. Apply policy to a Service
 
-```
-kubectl apply -f examples/waypoint/service-http-route.yaml
+```bash
+kubectl apply -f examples/waypoint/waypoint-http-route.yaml
 ```
 
 This `HTTPRoute` is a bit different; it has a parentRef of a `Service` rather
@@ -51,8 +51,8 @@ For more info, see the [GAMMA Initiative](https://gateway-api.sigs.k8s.io/mesh/g
 
 4. Send some traffic
 
-```
-CLIENT=$(kubectl get po -n httpbin -ojsonpath='{.items[0].metadata.name}')
+```bash
+CLIENT=$(kubectl get po -n httpbin -l app=curl -ojsonpath='{.items[0].metadata.name}')
 kubectl -n httpbin exec $CLIENT -- curl -sS -v httpbin:8000/get
 ```
 
@@ -60,6 +60,59 @@ You should see the header `Traversed-Waypoint` being added to the response,
 indicating the waypoint is now on the data path for all services in the `httpbin`
 namespace.
 
+## Istio Authorization Rules Demo
+
+Istio Authorization rules can be applied to the workloads that configured to use waypoint. In
+this section it's demonstrated on how to apply and test a simple Authorization.
+
+1. Apply the follow authorization policy that denies GET requests to httpbin service:
+
+kubectl apply -f examples/waypoint/httpbin-authz.yaml
+
+2. Test the traffic follow:
+
+Retrieve the client pod name as above:
+```bash
+CLIENT=$(kubectl get po -n httpbin -l app=curl -ojsonpath='{.items[0].metadata.name}')
+```
+
+- Test `GET` (that is blocked by the rule):
+
+  ```bash
+  kubectl -n httpbin exec $CLIENT -- curl -si httpbin:8000/get
+  ```
+
+  The request is blocked by the waypoint:
+
+  ```output
+  HTTP/1.1 403 Forbidden
+  traversed-waypoint: httpbin-waypoint
+  content-length: 19
+  content-type: text/plain
+  date: Wed, 26 Mar 2025 21:51:03 GMT
+  server: envoy
+  ```
+
+- When trying `POST` it is allowed:
+
+  ```bash
+  kubectl -n httpbin exec $CLIENT -- curl -sI -XPOST httpbin:8000/post
+  ```
+
+  Output shows the succesful transaction:
+
+  ```output
+  HTTP/1.1 200 OK
+  access-control-allow-credentials: true
+  access-control-allow-origin: *
+  content-type: application/json; charset=utf-8
+  date: Wed, 26 Mar 2025 21:56:09 GMT
+  content-length: 639
+  x-envoy-upstream-service-time: 2
+  traversed-waypoint: httpbin-waypoint
+  server: envoyCLIENT=$(kubectl get po -n httpbin -l app=curl -ojsonpath='{.items[0].metadata.name}')
+  ```
+
 ## Deploy on your own
 
 1. Enable ambient traffic capture in the namespace of the Services
diff --git a/examples/waypoint/httpbin-authz.yaml b/examples/waypoint/httpbin-authz.yaml
new file mode 100644
index 00000000000..12864347e12
--- /dev/null
+++ b/examples/waypoint/httpbin-authz.yaml
@@ -0,0 +1,16 @@
+apiVersion: security.istio.io/v1
+kind: AuthorizationPolicy
+metadata:
+  name: httpbin-authz-svc
+  namespace: httpbin
+spec:
+  action: DENY
+  rules:
+  - to:
+    - operation:
+        methods: ["GET"]
+        ports: ["8000"]
+  targetRefs:
+  - group: ""
+    kind: Service
+    name: httpbin
\ No newline at end of file
diff --git a/install/helm/kgateway/templates/role.yaml b/install/helm/kgateway/templates/role.yaml
index 5152a482dc5..3fe44189ef3 100644
--- a/install/helm/kgateway/templates/role.yaml
+++ b/install/helm/kgateway/templates/role.yaml
@@ -138,3 +138,11 @@ rules:
   - get
   - list
   - watch
+- apiGroups:
+  - security.istio.io
+  resources:
+  - authorizationpolicies
+  verbs:
+  - get
+  - list
+  - watch
diff --git a/internal/kgateway/extensions2/plugins/waypoint/rbac.go b/internal/kgateway/extensions2/plugins/waypoint/rbac.go
new file mode 100644
index 00000000000..c8702647dd6
--- /dev/null
+++ b/internal/kgateway/extensions2/plugins/waypoint/rbac.go
@@ -0,0 +1,158 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/settings"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+)
+
+const (
+	// TODO: Add configuration for trustDomain and trustDomainAliases in settings
+	// This will allow users to customize the trust domain and its aliases for their cluster
+	defaultTrustDomain = "cluster.local"
+)
+
+var (
+	// RootNamespace is the namespace where Istio control plane components are installed.
+	// It is set during initialization via SetRootNamespace() which reads from settings.IstioNamespace.
+	// The default value is "istio-system" if not configured.
+	RootNamespace = "istio-system"
+)
+
+// SetRootNamespace sets the RootNamespace from settings.
+// This should be called during initialization.
+func SetRootNamespace(s *settings.Settings) {
+	if s != nil {
+		RootNamespace = s.IstioNamespace
+	}
+}
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, ir.CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = ir.CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
+	policiesMap := model.AuthorizationPolicies{
+		NamespaceToPolicies: map[string][]model.AuthorizationPolicy{},
+		RootNamespace:       rootNamespace,
+	}
+
+	for _, policy := range policies {
+		convertedSpec := crdclient.TranslateObject(policy, gvk.AuthorizationPolicy, "").Spec.(*authpb.AuthorizationPolicy)
+		convertedPolicy := model.AuthorizationPolicy{
+			Name:        policy.Name,
+			Namespace:   policy.Namespace,
+			Annotations: map[string]string{},
+			Spec:        convertedSpec,
+		}
+		policiesMap.NamespaceToPolicies[policy.Namespace] = append(policiesMap.NamespaceToPolicies[policy.Namespace], convertedPolicy)
+	}
+
+	matcher := model.WorkloadPolicyMatcher{
+		IsWaypoint: true,
+		Services: []model.ServiceInfoForPolicyMatcher{
+			{
+				Name:      svc.GetName(),
+				Namespace: svc.GetNamespace(),
+				Registry:  svc.Provider(),
+			},
+		},
+		WorkloadNamespace: gatewayNamespace,
+		WorkloadLabels: map[string]string{
+			label.IoK8sNetworkingGatewayGatewayName.Name: gatewayName,
+		},
+	}
+
+	// Call the function
+	policyResult := policiesMap.ListAuthorizationPolicies(matcher)
+
+	if len(policyResult.Deny) == 0 && len(policyResult.Allow) == 0 &&
+		len(policyResult.Audit) == 0 && len(policyResult.Custom) == 0 {
+		return nil
+	}
+
+	trustBundle := trustdomain.NewBundle(defaultTrustDomain, nil)
+	builder := builder.New(trustBundle, nil, policyResult, builder.Option{
+		IsCustomBuilder: false,
+		UseFilterState:  true,
+	})
+
+	return builder
+}
+
+func applyHTTPRBACFilters(httpChain *ir.HttpFilterChainIR, httpRBAC []*ir.CustomEnvoyFilter, svc waypointquery.Service) {
+	// Apply RBAC filters regardless of the presence of proxy_protocol_authority
+	if len(httpRBAC) > 0 {
+		// Initialize CustomHTTPFilters if it's nil
+		if httpChain.CustomHTTPFilters == nil {
+			httpChain.CustomHTTPFilters = []ir.CustomEnvoyFilter{}
+		}
+
+		// Add RBAC filters to CustomHTTPFilters
+		for _, f := range httpRBAC {
+			httpChain.CustomHTTPFilters = append(httpChain.CustomHTTPFilters, *f)
+		}
+	}
+}
+
+func applyTCPRBACFilters(tcpChain *ir.TcpIR, tcpRBAC []*ir.CustomEnvoyFilter, svc waypointquery.Service) {
+	// Apply RBAC filters regardless of the presence of proxy_protocol_authority
+	if len(tcpRBAC) > 0 {
+		if tcpChain.NetworkFilters == nil {
+			tcpChain.NetworkFilters = []*anypb.Any{}
+		}
+
+		// Add RBAC filters as built-in network filters
+		for _, f := range tcpRBAC {
+			tcpChain.NetworkFilters = append(tcpChain.NetworkFilters, f.Config)
+		}
+	}
+}
diff --git a/internal/kgateway/extensions2/plugins/waypoint/testdata/input/authz.yaml b/internal/kgateway/extensions2/plugins/waypoint/testdata/input/authz.yaml
new file mode 100644
index 00000000000..c7fc7ab65d2
--- /dev/null
+++ b/internal/kgateway/extensions2/plugins/waypoint/testdata/input/authz.yaml
@@ -0,0 +1,89 @@
+---
+# The Namespace capture should apply to all the Services in the namespace
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: example-waypoint
+  namespace: infra
+spec:
+  gatewayClassName: kgateway-waypoint
+  listeners:
+  - name: proxy
+    port: 15088
+    protocol: istio.io/PROXY
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-a
+  namespace: infra
+spec:
+  clusterIP: 1.1.1.1
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-b
+  namespace: infra
+spec:
+  clusterIP: 2.2.2.2
+  ports:
+  - port: 9000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: helloworld
+  namespace: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+spec:
+  clusterIP: 1.2.3.4
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: helloworld    
+---
+# we should get a filter chain with two filters that reflect this policy
+# that denies GET requests to helloworld and svc-a services
+apiVersion: security.istio.io/v1
+kind: AuthorizationPolicy
+metadata:
+  name: helloworld-authz-svc
+  namespace: infra
+spec:
+  action: DENY
+  rules:
+  - to:
+    - operation:
+        methods:
+        - GET
+  targetRefs:
+  - group: ""
+    kind: Service
+    name: helloworld
+  - group: ""
+    kind: Service
+    name: svc-a
\ No newline at end of file
diff --git a/internal/kgateway/extensions2/plugins/waypoint/testdata/output/authz.yaml b/internal/kgateway/extensions2/plugins/waypoint/testdata/output/authz.yaml
new file mode 100644
index 00000000000..010a012aa6b
--- /dev/null
+++ b/internal/kgateway/extensions2/plugins/waypoint/testdata/output/authz.yaml
@@ -0,0 +1,197 @@
+Listeners:
+- address:
+    socketAddress:
+      address: '::'
+      ipv4Compat: true
+      portValue: 15088
+  filterChains:
+  - filterChainMatch:
+      destinationPort: 5000
+      prefixRanges:
+      - addressPrefix: 1.2.3.4
+        prefixLen: 32
+    filters:
+    - name: proxy_protocol_authority
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config
+        onNewConnection:
+        - factoryKey: envoy.string
+          formatString:
+            textFormatSource:
+              inlineString: '%DYNAMIC_METADATA(envoy.filters.listener.proxy_protocol:peer_principal)%'
+          objectKey: io.istio.peer_principal
+          sharedWithUpstream: ONCE
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        httpFilters:
+        - name: envoy.filters.http.rbac
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC
+            rules:
+              action: DENY
+              policies:
+                ns[infra]-policy[helloworld-authz-svc]-rule[0]:
+                  permissions:
+                  - andRules:
+                      rules:
+                      - orRules:
+                          rules:
+                          - header:
+                              name: :method
+                              stringMatch:
+                                exact: GET
+                  principals:
+                  - andIds:
+                      ids:
+                      - any: true
+            shadowRulesStatPrefix: istio_dry_run_allow_
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+        mergeSlashes: true
+        normalizePath: true
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: fc_http_5000_helloworld_infra
+        statPrefix: http
+        useRemoteAddress: true
+    name: fc_http_5000_helloworld_infra
+  - filterChainMatch:
+      destinationPort: 5000
+      prefixRanges:
+      - addressPrefix: 1.1.1.1
+        prefixLen: 32
+    filters:
+    - name: proxy_protocol_authority
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config
+        onNewConnection:
+        - factoryKey: envoy.string
+          formatString:
+            textFormatSource:
+              inlineString: '%DYNAMIC_METADATA(envoy.filters.listener.proxy_protocol:peer_principal)%'
+          objectKey: io.istio.peer_principal
+          sharedWithUpstream: ONCE
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        httpFilters:
+        - name: envoy.filters.http.rbac
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC
+            rules:
+              action: DENY
+              policies:
+                ns[infra]-policy[helloworld-authz-svc]-rule[0]:
+                  permissions:
+                  - andRules:
+                      rules:
+                      - orRules:
+                          rules:
+                          - header:
+                              name: :method
+                              stringMatch:
+                                exact: GET
+                  principals:
+                  - andIds:
+                      ids:
+                      - any: true
+            shadowRulesStatPrefix: istio_dry_run_allow_
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+        mergeSlashes: true
+        normalizePath: true
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: fc_http_5000_svc-a_infra
+        statPrefix: http
+        useRemoteAddress: true
+    name: fc_http_5000_svc-a_infra
+  - filterChainMatch:
+      destinationPort: 9000
+      prefixRanges:
+      - addressPrefix: 2.2.2.2
+        prefixLen: 32
+    filters:
+    - name: proxy_protocol_authority
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config
+        onNewConnection:
+        - factoryKey: envoy.string
+          formatString:
+            textFormatSource:
+              inlineString: '%DYNAMIC_METADATA(envoy.filters.listener.proxy_protocol:peer_principal)%'
+          objectKey: io.istio.peer_principal
+          sharedWithUpstream: ONCE
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+        mergeSlashes: true
+        normalizePath: true
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: fc_http_9000_svc-b_infra
+        statPrefix: http
+        useRemoteAddress: true
+    name: fc_http_9000_svc-b_infra
+  listenerFilters:
+  - name: envoy.filters.listener.proxy_protocol
+    typedConfig:
+      '@type': type.googleapis.com/envoy.extensions.filters.listener.proxy_protocol.v3.ProxyProtocol
+      rules:
+      - onTlvPresent:
+          key: peer_principal
+        tlvType: 208
+  name: proxy_protocol_inbound
+Routes:
+- ignorePortInHostMatching: true
+  name: fc_http_5000_helloworld_infra
+  virtualHosts:
+  - domains:
+    - '*'
+    name: vh_http_5000_helloworld_infra
+    routes:
+    - match:
+        prefix: /
+      name: vh_http_5000_helloworld_infra-route-0-matcher-0
+      route:
+        cluster: kube_infra_helloworld_5000
+        clusterNotFoundResponseCode: INTERNAL_SERVER_ERROR
+- ignorePortInHostMatching: true
+  name: fc_http_5000_svc-a_infra
+  virtualHosts:
+  - domains:
+    - '*'
+    name: vh_http_5000_svc-a_infra
+    routes:
+    - match:
+        prefix: /
+      name: vh_http_5000_svc-a_infra-route-0-matcher-0
+      route:
+        cluster: kube_infra_svc-a_5000
+        clusterNotFoundResponseCode: INTERNAL_SERVER_ERROR
+- ignorePortInHostMatching: true
+  name: fc_http_9000_svc-b_infra
+  virtualHosts:
+  - domains:
+    - '*'
+    name: vh_http_9000_svc-b_infra
+    routes:
+    - match:
+        prefix: /
+      name: vh_http_9000_svc-b_infra-route-0-matcher-0
+      route:
+        cluster: kube_infra_svc-b_9000
+        clusterNotFoundResponseCode: INTERNAL_SERVER_ERROR
diff --git a/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator.go b/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator.go
index a059c603876..b93c9c09450 100644
--- a/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator.go
+++ b/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator.go
@@ -24,6 +24,7 @@ import (
 	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/wellknown"
 	"github.com/kgateway-dev/kgateway/v2/pkg/utils/stringutils"
 
+	istiosecurity "istio.io/client-go/pkg/apis/security/v1"
 	"istio.io/istio/pkg/kube/krt"
 	"istio.io/istio/pkg/slices"
 	"istio.io/istio/pkg/util/sets"
@@ -83,6 +84,7 @@ func (w *waypointTranslator) Translate(
 		attachedRoutes.Insert(namespacedName(hr))
 	}
 
+	authzPolicies := w.waypointQueries.GetAuthorizationPolicies(kctx, ctx, gateway.Namespace, RootNamespace)
 	waypointFor := waypointquery.GetWaypointFor(gateway.Obj)
 
 	if waypointFor.ForService() {
@@ -95,6 +97,7 @@ func (w *waypointTranslator) Translate(
 			routes,
 			gwListener,
 			attachedRoutes,
+			authzPolicies,
 		)
 		proxyListener.HttpFilterChain = append(proxyListener.HttpFilterChain, http...)
 		proxyListener.TcpFilterChain = append(proxyListener.TcpFilterChain, tcp...)
@@ -236,6 +239,7 @@ func (t *waypointTranslator) buildServiceChains(
 	gwRoutes []*query.RouteInfo,
 	gwListener *ir.Listener,
 	attachedRoutes sets.Set[types.NamespacedName],
+	authzPolicies []*istiosecurity.AuthorizationPolicy,
 ) ([]ir.HttpFilterChainIR, []ir.TcpIR) {
 	var httpOut []ir.HttpFilterChainIR
 	var tcpOut []ir.TcpIR
@@ -253,6 +257,8 @@ func (t *waypointTranslator) buildServiceChains(
 	// * Just forward traffic
 	// * TODO TCPRoute
 	for _, svc := range services {
+		tcpRBAC, httpRBAC := BuildRBACForService(authzPolicies, gw.Obj, &svc)
+
 		// get Service-specific routes
 		httpRoutes := gwRoutes
 		svcRoutes := t.waypointQueries.GetHTTPRoutesForService(kctx, ctx, &svc)
@@ -290,15 +296,24 @@ func (t *waypointTranslator) buildServiceChains(
 					// that just forwards traffic
 					virtualHostForPort = buildDefaultToPortVirtualHost(svc, svcPort)
 				}
-				httpOut = append(httpOut, ir.HttpFilterChainIR{
+				httpChain := ir.HttpFilterChainIR{
 					FilterChainCommon: filterChain,
 					Vhosts:            []*ir.VirtualHost{virtualHostForPort},
-				})
+				}
+
+				// Apply HTTP RBAC filters to this HTTP filter chain
+				applyHTTPRBACFilters(&httpChain, httpRBAC, svc)
+				httpOut = append(httpOut, httpChain)
 			} else {
-				tcpOut = append(tcpOut, ir.TcpIR{
+				tcpChain := ir.TcpIR{
 					FilterChainCommon: filterChain,
 					BackendRefs:       []ir.BackendRefIR{svc.BackendRef(svcPort)},
-				})
+				}
+
+				// Apply TCP RBAC filters to this TCP filter chain
+				applyTCPRBACFilters(&tcpChain, tcpRBAC, svc)
+
+				tcpOut = append(tcpOut, tcpChain)
 			}
 		}
 	}
diff --git a/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator_test.go b/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator_test.go
index 19a6edb30ac..0903718b9c6 100644
--- a/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator_test.go
+++ b/internal/kgateway/extensions2/plugins/waypoint/waypoint_translator_test.go
@@ -30,6 +30,7 @@ var cases = []struct {
 	{"HTTPRoute on Service", "httproute-svc", exampleGw, ""},
 	{"HTTPRoute on ServiceEntry", "httproute-se", exampleGw, ""},
 	{"HTTPRoute on ServiceEntry via Hostname", "httproute-se-hostname", exampleGw, ""},
+	{"Authz Policies", "authz", exampleGw, ""},
 }
 
 func TestWaypointTranslator(t *testing.T) {
diff --git a/internal/kgateway/extensions2/plugins/waypoint/waypointquery/service_model.go b/internal/kgateway/extensions2/plugins/waypoint/waypointquery/service_model.go
index d226d89267f..c23dd35a08b 100644
--- a/internal/kgateway/extensions2/plugins/waypoint/waypointquery/service_model.go
+++ b/internal/kgateway/extensions2/plugins/waypoint/waypointquery/service_model.go
@@ -17,6 +17,7 @@ import (
 	istionetworking "istio.io/client-go/pkg/apis/networking/v1"
 	networkingclient "istio.io/client-go/pkg/apis/networking/v1"
 	istioutil "istio.io/istio/pilot/pkg/networking/util"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
 	"istio.io/istio/pkg/slices"
 
 	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/kubernetes"
@@ -136,6 +137,15 @@ func (s Service) Hostname() string {
 	return s.Hostnames[0]
 }
 
+// Provider returns the appropriate service provider based on the service type.
+// Returns provider.Kubernetes for regular Kubernetes services and provider.External for ServiceEntries.
+func (s Service) Provider() provider.ID {
+	if s.GroupKind.Kind == wellknown.ServiceEntryGVK.Kind {
+		return provider.External
+	}
+	return provider.Kubernetes
+}
+
 // ErrNoServiceVIPs should occur only due to headless Services that have no
 // ClusterIP, or ServiceEntry that don't have addresses in Spec or Status.
 // We also validate the format of these addresses are either IPs or CIDR ranges,
diff --git a/internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_authz.go b/internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_authz.go
new file mode 100644
index 00000000000..30800ffc988
--- /dev/null
+++ b/internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_authz.go
@@ -0,0 +1,35 @@
+package waypointquery
+
+import (
+	"context"
+
+	istiosecurity "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pkg/kube/krt"
+)
+
+func (w *waypointQueries) GetAuthorizationPolicies(kctx krt.HandlerContext, ctx context.Context, targetNamespace, rootNamespace string) []*istiosecurity.AuthorizationPolicy {
+	// Get all policies in the target namespace
+	policies := krt.Fetch(kctx, w.authzPolicies, krt.FilterIndex(w.byNamespace, targetNamespace))
+
+	// Get all policies in the root namespace
+	if rootNamespace != "" && rootNamespace != targetNamespace {
+		rootPolicies := krt.Fetch(kctx, w.authzPolicies, krt.FilterIndex(w.byNamespace, rootNamespace))
+		policies = append(policies, rootPolicies...)
+	}
+
+	// Filter policies to only include those targeting services in the target namespace
+	filteredPolicies := make([]*istiosecurity.AuthorizationPolicy, 0, len(policies))
+	for _, policy := range policies {
+		for _, targetRef := range policy.Spec.GetTargetRefs() {
+			if targetRef.GetKind() == "Service" && targetRef.GetGroup() == "" {
+				// If the policy targets a service in the target namespace, include it
+				targetNamespaceMatches := targetRef.GetNamespace() == "" || targetRef.GetNamespace() == targetNamespace
+				if targetNamespaceMatches {
+					filteredPolicies = append(filteredPolicies, policy)
+					break
+				}
+			}
+		}
+	}
+	return filteredPolicies
+}
diff --git a/internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_query.go b/internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_query.go
index 8c2268a913d..62d1764373b 100644
--- a/internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_query.go
+++ b/internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_query.go
@@ -5,7 +5,11 @@ import (
 	"fmt"
 
 	"istio.io/api/label"
+	istiosecurity "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pkg/config/schema/gvr"
+	"istio.io/istio/pkg/kube/kclient"
 	"istio.io/istio/pkg/kube/krt"
+	"istio.io/istio/pkg/kube/kubetypes"
 	"istio.io/istio/pkg/slices"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -37,6 +41,9 @@ type WaypointQueries interface {
 	// GetHTTPRoutesForService fetches HTTPRoutes that have the given Service in parentRefs.
 	GetHTTPRoutesForService(kctx krt.HandlerContext, ctx context.Context, svc *Service) []query.RouteInfo
 
+	// GetAuthorizationPolicies gets a filtered list of policies in the namespaces that also target services in the targetNamespace
+	GetAuthorizationPolicies(kctx krt.HandlerContext, ctx context.Context, targetNamespace, rootNamespace string) []*istiosecurity.AuthorizationPolicy
+
 	HasSynced() bool
 }
 
@@ -45,11 +52,23 @@ func NewQueries(
 	gwQueries query.GatewayQueries,
 ) WaypointQueries {
 	waypointedServices, servicesByWaypoint := waypointAttachmentIndex(commonCols)
+	authzInformer := kclient.NewDelayedInformer[*istiosecurity.AuthorizationPolicy](
+		commonCols.Client,
+		gvr.AuthorizationPolicy,
+		kubetypes.StandardInformer,
+		kclient.Filter{ObjectFilter: commonCols.Client.ObjectFilter()},
+	)
+	authzPolicies := krt.WrapClient(authzInformer, commonCols.KrtOpts.ToOptions("AuthorizationPolicies")...)
+	byNamespace := krt.NewIndex(authzPolicies, func(p *istiosecurity.AuthorizationPolicy) []string {
+		return []string{p.GetNamespace()}
+	})
 	return &waypointQueries{
 		queries:            gwQueries,
 		commonCols:         commonCols,
 		waypointedServices: waypointedServices,
 		servicesByWaypoint: servicesByWaypoint,
+		authzPolicies:      authzPolicies,
+		byNamespace:        byNamespace,
 	}
 }
 
@@ -59,12 +78,15 @@ type waypointQueries struct {
 
 	waypointedServices krt.Collection[WaypointedService]
 	servicesByWaypoint krt.Index[types.NamespacedName, WaypointedService]
+	authzPolicies      krt.Collection[*istiosecurity.AuthorizationPolicy]
+	byNamespace        krt.Index[string, *istiosecurity.AuthorizationPolicy]
 }
 
 func (w *waypointQueries) HasSynced() bool {
-	return w.waypointedServices.HasSynced()
+	waypointSync := w.waypointedServices.HasSynced()
+	authzSync := w.authzPolicies.HasSynced()
+	return waypointSync && authzSync
 }
-
 func (w *waypointQueries) GetHTTPRoutesForService(
 	kctx krt.HandlerContext,
 	ctx context.Context,
diff --git a/internal/kgateway/extensions2/settings/settings.go b/internal/kgateway/extensions2/settings/settings.go
index c90cb9bf5e3..bf1bea4dde4 100644
--- a/internal/kgateway/extensions2/settings/settings.go
+++ b/internal/kgateway/extensions2/settings/settings.go
@@ -16,6 +16,10 @@ type Settings struct {
 	EnableIstioIntegration bool `split_words:"true"`
 	EnableIstioAutoMtls    bool `split_words:"true"`
 
+	// IstioNamespace is the namespace where Istio control plane components are installed.
+	// Defaults to "istio-system".
+	IstioNamespace string `split_words:"true" default:"istio-system"`
+
 	// XdsServiceName is the name of the Kubernetes Service that serves xDS config.
 	// It it assumed to be in the kgateway install namespace.
 	XdsServiceName string `split_words:"true" default:"kgateway"`
diff --git a/internal/kgateway/extensions2/settings/settings_test.go b/internal/kgateway/extensions2/settings/settings_test.go
index 773a2394776..06f689ae041 100644
--- a/internal/kgateway/extensions2/settings/settings_test.go
+++ b/internal/kgateway/extensions2/settings/settings_test.go
@@ -33,6 +33,7 @@ func TestSettings(t *testing.T) {
 				DnsLookupFamily:        "V4_PREFERRED",
 				EnableIstioIntegration: false,
 				EnableIstioAutoMtls:    false,
+				IstioNamespace:         "istio-system",
 				XdsServiceName:         wellknown.DefaultXdsService,
 				XdsServicePort:         wellknown.DefaultXdsPort,
 				UseRustFormations:      false,
@@ -64,6 +65,7 @@ func TestSettings(t *testing.T) {
 				DnsLookupFamily:        "V4_ONLY",
 				EnableIstioIntegration: true,
 				EnableIstioAutoMtls:    true,
+				IstioNamespace:         "istio-system",
 				XdsServiceName:         "custom-svc",
 				XdsServicePort:         1234,
 				UseRustFormations:      true,
@@ -98,6 +100,7 @@ func TestSettings(t *testing.T) {
 			expectedSettings: &settings.Settings{
 				DnsLookupFamily:        "V4_PREFERRED",
 				EnableIstioAutoMtls:    true,
+				IstioNamespace:         "istio-system",
 				XdsServiceName:         wellknown.DefaultXdsService,
 				XdsServicePort:         wellknown.DefaultXdsPort,
 				DefaultImageRegistry:   "cr.kgateway.dev",
diff --git a/internal/kgateway/ir/filters.go b/internal/kgateway/ir/filters.go
new file mode 100644
index 00000000000..793af805f02
--- /dev/null
+++ b/internal/kgateway/ir/filters.go
@@ -0,0 +1,74 @@
+package ir
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+)
+
+func CustomNetworkFilters(
+	extraFilters []*listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) []*CustomEnvoyFilter {
+	customFilters := make([]*CustomEnvoyFilter, 0, len(extraFilters))
+	for _, f := range extraFilters {
+		customFilters = append(customFilters, CustomNetworkFilter(f, stage, predicate))
+	}
+	return customFilters
+}
+
+func CustomNetworkFilter(
+	f *listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) *CustomEnvoyFilter {
+	config := f.GetTypedConfig()
+	if config == nil {
+		return nil
+	}
+
+	return customFiltersHelper(stage, predicate, f.GetName(), config)
+}
+
+func CustomHTTPFilters(
+	extraFilters []*hcmv3.HttpFilter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) []*CustomEnvoyFilter {
+	customFilters := make([]*CustomEnvoyFilter, 0, len(extraFilters))
+	for _, f := range extraFilters {
+		customFilters = append(customFilters, CustomHTTPFilter(f, stage, predicate))
+	}
+	return customFilters
+}
+
+func CustomHTTPFilter(
+	f *hcmv3.HttpFilter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) *CustomEnvoyFilter {
+	config := f.GetTypedConfig()
+
+	return customFiltersHelper(stage, predicate, f.GetName(), config)
+}
+
+func customFiltersHelper(
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+	name string,
+	config *anypb.Any,
+) *CustomEnvoyFilter {
+	return &CustomEnvoyFilter{
+		FilterStage: plugins.FilterStage[plugins.WellKnownFilterStage]{
+			RelativeTo: plugins.WellKnownFilterStage(int(stage)),
+			Weight:     int(predicate),
+		},
+		Name:   name,
+		Config: config,
+	}
+}
diff --git a/internal/kgateway/ir/gw2.go b/internal/kgateway/ir/gw2.go
index 1a828032019..25b0601dde4 100644
--- a/internal/kgateway/ir/gw2.go
+++ b/internal/kgateway/ir/gw2.go
@@ -78,6 +78,7 @@ type FilterChainCommon struct {
 	Matcher              FilterChainMatch
 	FilterChainName      string
 	CustomNetworkFilters []CustomEnvoyFilter
+	NetworkFilters       []*anypb.Any
 	TLS                  *TlsBundle
 }
 
@@ -95,6 +96,7 @@ type HttpFilterChainIR struct {
 	Vhosts                  []*VirtualHost
 	AttachedPolicies        AttachedPolicies
 	AttachedNetworkPolicies AttachedPolicies
+	CustomHTTPFilters       []CustomEnvoyFilter
 }
 
 type TcpIR struct {
diff --git a/internal/kgateway/setup/setup.go b/internal/kgateway/setup/setup.go
index 298c28e580b..c351b15bc15 100644
--- a/internal/kgateway/setup/setup.go
+++ b/internal/kgateway/setup/setup.go
@@ -23,6 +23,7 @@ import (
 	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/controller"
 	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/common"
 	extensionsplug "github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugin"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint"
 	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/settings"
 	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/krtcollections"
 	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/utils/krtutil"
@@ -67,6 +68,9 @@ func StartKgateway(
 	}
 	logger.Info(fmt.Sprintf("got settings from env: %+v", *st))
 
+	// Set the Istio namespace from settings
+	waypoint.SetRootNamespace(st)
+
 	uniqueClientCallbacks, uccBuilder := krtcollections.NewUniquelyConnectedClients()
 	cache, err := startControlPlane(ctx, st.XdsServicePort, uniqueClientCallbacks)
 	if err != nil {
diff --git a/internal/kgateway/translator/gateway/testutils/test_translator.go b/internal/kgateway/translator/gateway/testutils/test_translator.go
index a491a837e9d..e45051dd5d6 100644
--- a/internal/kgateway/translator/gateway/testutils/test_translator.go
+++ b/internal/kgateway/translator/gateway/testutils/test_translator.go
@@ -206,6 +206,7 @@ func (tc TestCase) Run(t test.Failer, ctx context.Context) (map[types.Namespaced
 		gvr.TLSRoute,
 		gvr.ServiceEntry,
 		gvr.WorkloadEntry,
+		gvr.AuthorizationPolicy,
 	} {
 		clienttest.MakeCRD(t, cli, crd)
 	}
diff --git a/internal/kgateway/translator/irtranslator/fc.go b/internal/kgateway/translator/irtranslator/fc.go
index d626ba41977..ef1ac4d3066 100644
--- a/internal/kgateway/translator/irtranslator/fc.go
+++ b/internal/kgateway/translator/irtranslator/fc.go
@@ -297,7 +297,7 @@ func (h *hcmNetworkFilterTranslator) computeHttpFilters(ctx context.Context, l i
 			httpFilters = append(httpFilters, httpFilter)
 		}
 	}
-	//	httpFilters = append(httpFilters, CustomHttpFilters(h.listener)...)
+	httpFilters = append(httpFilters, convertCustomHttpFilters(l.CustomHTTPFilters)...)
 
 	// https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_filters#filter-ordering
 	// HttpFilter ordering determines the order in which the HCM will execute the filter.
@@ -340,6 +340,23 @@ func (h *hcmNetworkFilterTranslator) computeHttpFilters(ctx context.Context, l i
 	return envoyHttpFilters
 }
 
+func convertCustomHttpFilters(customHttpFilters []ir.CustomEnvoyFilter) []plugins.StagedHttpFilter {
+	var out []plugins.StagedHttpFilter
+	for _, customFilter := range customHttpFilters {
+		stagedFilter := plugins.StagedHttpFilter{
+			Filter: &envoyhttp.HttpFilter{
+				Name: customFilter.Name,
+				ConfigType: &envoyhttp.HttpFilter_TypedConfig{
+					TypedConfig: customFilter.Config,
+				},
+			},
+			Stage: customFilter.FilterStage,
+		}
+		out = append(out, stagedFilter)
+	}
+	return out
+}
+
 func sortHttpFilters(filters plugins.StagedHttpFilterList) []*envoyhttp.HttpFilter {
 	sort.Sort(filters)
 	var sortedFilters []*envoyhttp.HttpFilter
diff --git a/test/kubernetes/e2e/features/waypoint/cases_httproute.go b/test/kubernetes/e2e/features/waypoint/cases_httproute.go
index 5a969f8cb7a..868a39bceeb 100644
--- a/test/kubernetes/e2e/features/waypoint/cases_httproute.go
+++ b/test/kubernetes/e2e/features/waypoint/cases_httproute.go
@@ -24,6 +24,17 @@ var (
 		},
 		Body: gstruct.Ignore(),
 	}
+
+	isOK = matchers.HttpResponse{
+		StatusCode: http.StatusOK,
+		Body:       gstruct.Ignore(),
+	}
+
+	// Response is forbidden
+	isForbidden = matchers.HttpResponse{
+		StatusCode: http.StatusForbidden,
+		Body:       gstruct.Ignore(),
+	}
 )
 
 func (s *testingSuite) TestServiceEntryHostnameHTTPRoute() {
@@ -61,3 +72,14 @@ func (s *testingSuite) TestGatewayHTTPRoute() {
 	s.assertCurlService(fromCurl, "svc-a", testNamespace, hasHTTPRoute)
 	s.assertCurlService(fromCurl, "svc-b", testNamespace, hasHTTPRoute)
 }
+
+func (s *testingSuite) TestGatewayAttachedAuthorizationPolicy() {
+	s.setNamespaceWaypointOrFail(testNamespace)
+	s.applyOrFail("authz-l7.yaml", testNamespace)
+
+	// both get the route since we parent to the Gateway
+	s.assertCurlService(fromCurl, "svc-a", testNamespace, isOK)
+	s.assertCurlService(fromCurl, "svc-b", testNamespace, isOK)
+	s.assertCurlService(fromNotCurl, "svc-a", testNamespace, isOK)
+	s.assertCurlService(fromNotCurl, "svc-b", testNamespace, isForbidden)
+}
diff --git a/test/kubernetes/e2e/features/waypoint/curl_helper.go b/test/kubernetes/e2e/features/waypoint/curl_helper.go
index 60b75bc7055..d0ad183961f 100644
--- a/test/kubernetes/e2e/features/waypoint/curl_helper.go
+++ b/test/kubernetes/e2e/features/waypoint/curl_helper.go
@@ -14,9 +14,8 @@ import (
 var (
 	testAppPort = 8080
 
-	fromCurl = kubectl.PodExecOptions{Name: "curl", Namespace: testNamespace, Container: "curl"}
-	// TODO this will be used for testing AuthorizationPolicy
-	// fromNotCurl = kubectl.PodExecOptions{Name: "notcurl", Namespace: testNamespace, Container: "notcurl"}
+	fromCurl    = kubectl.PodExecOptions{Name: "curl", Namespace: testNamespace, Container: "curl"}
+	fromNotCurl = kubectl.PodExecOptions{Name: "notcurl", Namespace: testNamespace, Container: "notcurl"}
 )
 
 func (s *testingSuite) assertCurlService(
diff --git a/test/kubernetes/e2e/features/waypoint/suite.go b/test/kubernetes/e2e/features/waypoint/suite.go
index 65096ea5d25..3bd61d80368 100644
--- a/test/kubernetes/e2e/features/waypoint/suite.go
+++ b/test/kubernetes/e2e/features/waypoint/suite.go
@@ -72,7 +72,8 @@ func (s *testingSuite) SetupSuite() {
 	}{
 		{"app", "svc-a"},
 		{"app", "svc-b"},
-		{"app", "svc-b"},
+		{"app", "curl"},
+		{"app", "notcurl"},
 		{"gateway.networking.k8s.io/gateway-name", gwName},
 	}
 	for _, app := range wantApps {
diff --git a/test/kubernetes/e2e/features/waypoint/testdata/authz-l7.yaml b/test/kubernetes/e2e/features/waypoint/testdata/authz-l7.yaml
new file mode 100644
index 00000000000..243513bb555
--- /dev/null
+++ b/test/kubernetes/e2e/features/waypoint/testdata/authz-l7.yaml
@@ -0,0 +1,23 @@
+# Please edit the object below. Lines beginning with a '#' will be ignored,
+# and an empty file will abort the edit. If an error occurs while saving this file will be
+# reopened with the relevant failures.
+#
+apiVersion: security.istio.io/v1
+kind: AuthorizationPolicy
+metadata:
+  name: authz-l7-demy-notcurl-svc-b
+  namespace: waypoint-test-ns
+spec:
+  action: DENY
+  rules:
+  - from:
+    - source:
+        principals: ["cluster.local/ns/waypoint-test-ns/sa/notcurl"]
+    to:
+    - operation:
+        methods: ["GET"]
+        ports: ["8080"]
+  targetRefs:
+  - group: ""
+    kind: Service
+    name: svc-b
\ No newline at end of file
diff --git a/test/kubernetes/e2e/tests/waypoint_test.go b/test/kubernetes/e2e/tests/waypoint_test.go
index 5c66214921b..23a0193f368 100644
--- a/test/kubernetes/e2e/tests/waypoint_test.go
+++ b/test/kubernetes/e2e/tests/waypoint_test.go
@@ -14,6 +14,12 @@ import (
 
 func TestKgatewayWaypoint(t *testing.T) {
 	ctx := context.Background()
+
+	// Set Istio version if not already set
+	if os.Getenv("ISTIO_VERSION") == "" {
+		os.Setenv("ISTIO_VERSION", "1.23.0") // Using minimum required version for ambient mode
+	}
+
 	installNs, nsEnvPredefined := envutils.LookupOrDefault(testutils.InstallNamespace, "kgateway-waypoint-test")
 	testInstallation := e2e.CreateTestInstallation(
 		t,


Reviews:
user: login: lgadban
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T13:38:12Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:39:50Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:40:18Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:42:02Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:44:17Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:44:46Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:49:49Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:51:05Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:51:40Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:53:37Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:54:17Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:54:26Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:55:12Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T04:10:50Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T04:14:35Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T04:18:08Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T04:18:38Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T04:20:03Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T04:26:54Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T05:09:09Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T06:22:15Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T06:28:33Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T06:34:45Z
user: login: PetrMc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T06:35:42Z
user: login: lgadban
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:30:33Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:17:20Z
user: login: stevenctl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:19:16Z
user: login: stevenctl
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T21:57:32Z
user: login: lgadban
body: Excellent work on this
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T00:28:53Z

Review comments:
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: lgadban
body: Is this correct? I only see two lists here
created_at: 2025-03-26T13:14:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -45,11 +52,24 @@ func NewQueries(
 	gwQueries query.GatewayQueries,
 ) WaypointQueries {
 	waypointedServices, servicesByWaypoint := waypointAttachmentIndex(commonCols)
+	// authzInformer is
path: internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_query.go
user: login: lgadban
body: I think this is unintentional but can we either remove this or expand the comment?
created_at: 2025-03-26T13:21:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,6 +41,9 @@ type WaypointQueries interface {
 	// GetHTTPRoutesForService fetches HTTPRoutes that have the given Service in parentRefs.
 	GetHTTPRoutesForService(kctx krt.HandlerContext, ctx context.Context, svc *Service) []query.RouteInfo
 
+	// GetAuthorizationPolicies gets all AuthorizationPolicy resources in the targetNamespace and rootNamespace.
path: internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_query.go
user: login: lgadban
body: I think this may be inaccurate, we're not getting all authzPols in these namespaces but a filtered list of pols in the namespaces that also target services in the targetNamespace?
created_at: 2025-03-26T13:30:42Z
author_association: CONTRIBUTOR
path: internal/kgateway/extensions2/plugins/waypoint/rbac_filters.go
user: login: lgadban
body: very small nit: is it really worth having a separate file for just these 2 functions?
created_at: 2025-03-26T13:33:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+)
+
+// applyHTTPRBACFilters applies RBAC filters to an HTTP filter chain
+func applyHTTPRBACFilters(httpChain *ir.HttpFilterChainIR, httpRBAC []*ir.CustomEnvoyFilter, svc waypointquery.Service) {
+	// Apply RBAC filters regardless of the presence of proxy_protocol_authority
path: internal/kgateway/extensions2/plugins/waypoint/rbac_filters.go
user: login: lgadban
body: this comment might deserve some expansion for readers not as close to the waypoint/sandwich internals
created_at: 2025-03-26T13:34:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: lgadban
body: I'm assuming we do the same on the GG side?
cc @stevenctl 
created_at: 2025-03-26T13:38:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: holdover from the original impl; 2 is correct now
created_at: 2025-03-26T16:39:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: yes
created_at: 2025-03-26T16:40:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+)
+
+// applyHTTPRBACFilters applies RBAC filters to an HTTP filter chain
+func applyHTTPRBACFilters(httpChain *ir.HttpFilterChainIR, httpRBAC []*ir.CustomEnvoyFilter, svc waypointquery.Service) {
+	// Apply RBAC filters regardless of the presence of proxy_protocol_authority
+	if len(httpRBAC) > 0 {
+		// Initialize CustomHTTPFilters if it's nil
+		if httpChain.CustomHTTPFilters == nil {
+			httpChain.CustomHTTPFilters = []ir.CustomEnvoyFilter{}
path: internal/kgateway/extensions2/plugins/waypoint/rbac_filters.go
user: login: stevenctl
body: this init doesn't add anything vs just appending; you could do `make([]type, 0, capacity)` but I think just appending is fine. 
created_at: 2025-03-26T16:42:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+)
+
+// applyHTTPRBACFilters applies RBAC filters to an HTTP filter chain
+func applyHTTPRBACFilters(httpChain *ir.HttpFilterChainIR, httpRBAC []*ir.CustomEnvoyFilter, svc waypointquery.Service) {
+	// Apply RBAC filters regardless of the presence of proxy_protocol_authority
path: internal/kgateway/extensions2/plugins/waypoint/rbac_filters.go
user: login: stevenctl
body: I think we can elide this comment. The whole `proxy_protocol_authority` filter is a detail we can consider orthogonal to building the authz policy. Maybe a comment near where we call these `applyXXXRBACFilters` funcs to say "these will read the source identity because of the proxy_protocol authority filter (see sandwich plugin)"
created_at: 2025-03-26T16:44:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
+	policiesMap := model.AuthorizationPolicies{
+		NamespaceToPolicies: map[string][]model.AuthorizationPolicy{},
+		RootNamespace:       rootNamespace,
+	}
+
+	for _, policy := range policies {
+		convertedSpec := crdclient.TranslateObject(policy, gvk.AuthorizationPolicy, "").Spec.(*authpb.AuthorizationPolicy)
+		convertedPolicy := model.AuthorizationPolicy{
+			Name:        policy.Name,
+			Namespace:   policy.Namespace,
+			Annotations: map[string]string{},
+			Spec:        convertedSpec,
+		}
+		policiesMap.NamespaceToPolicies[policy.Namespace] = append(policiesMap.NamespaceToPolicies[policy.Namespace], convertedPolicy)
+	}
+
+	matcher := model.WorkloadPolicyMatcher{
+		IsWaypoint: true,
+		Services: []model.ServiceInfoForPolicyMatcher{
+			{
+				Name:      svc.GetName(),
+				Namespace: svc.GetNamespace(),
+				Registry:  provider.Kubernetes,
+			},
+		},
+		WorkloadNamespace: gatewayNamespace,
+		WorkloadLabels: map[string]string{
+			label.IoK8sNetworkingGatewayGatewayName.Name: gatewayName,
+		},
+	}
+
+	// Call the function
+	policyResult := policiesMap.ListAuthorizationPolicies(matcher)
+
+	if len(policyResult.Deny) == 0 && len(policyResult.Allow) == 0 &&
+		len(policyResult.Audit) == 0 && len(policyResult.Custom) == 0 {
+		return nil
+	}
+
+	trustBundle := trustdomain.NewBundle("cluster.local", nil)
+	builder := builder.New(trustBundle, nil, policyResult, builder.Option{
+		IsCustomBuilder: false,
+		UseFilterState:  true,
+	})
+
+	return builder
+}
+
+func CustomNetworkFilters(
+	extraFilters []*listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) []*ir.CustomEnvoyFilter {
+	customFilters := make([]*ir.CustomEnvoyFilter, 0, len(extraFilters))
+	for _, f := range extraFilters {
+		customFilters = append(customFilters, CustomNetworkFilter(f, stage, predicate))
+	}
+	return customFilters
+}
+
+func CustomNetworkFilter(
+	f *listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) *ir.CustomEnvoyFilter {
+	config := f.GetTypedConfig()
+	if config == nil {
+		return nil
+	}
+
+	return customFiltersHelper(stage, predicate, f.GetName(), config)
+}
+
+func CustomHTTPFilters(
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: We may already have some funcs like this in the `ir` package. If not, these should probably just live there. 
created_at: 2025-03-26T16:44:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,35 @@
+package waypointquery
+
+import (
+	"context"
+
+	istiosecurity "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pkg/kube/krt"
+)
+
+func (w *waypointQueries) GetAuthorizationPolicies(kctx krt.HandlerContext, ctx context.Context, targetNamespace, rootNamespace string) []*istiosecurity.AuthorizationPolicy {
path: internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_authz.go
user: login: stevenctl
body: We don't need to do this now, but there is room for optimization here. Right now, this means the entire Gateway IR will be recomputed when any authz policies in these namespaces change. Instead we can:

1. Make this func accept the Service instead of the targetNamespace
2. Build an index of AuthzPolicies that actually target our Service
3. Make another func that accepts the Gateway
4. Build a similar index (could be a single index that includes `groupkind`)

This would look something like the RouteIndex 

https://github.com/kgateway-dev/kgateway/blob/2eebf1c68a5fa65143fc92376a25c8c179fee303/internal/kgateway/krtcollections/policy.go#L474

https://github.com/kgateway-dev/kgateway/blob/2eebf1c68a5fa65143fc92376a25c8c179fee303/internal/kgateway/krtcollections/policy.go#L564
created_at: 2025-03-26T16:49:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+---
+# The Namespace capture should apply to all the Services in the namespace
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: example-waypoint
+  namespace: infra
+spec:
+  gatewayClassName: kgateway-waypoint
+  listeners:
+  - name: proxy
+    port: 15088
+    protocol: istio.io/PROXY
+---
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-a
+  namespace: infra
+spec:
+  clusterIP: 1.1.1.1
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-b
+  namespace: infra
+spec:
+  clusterIP: 2.2.2.2
+  ports:
+  - port: 9000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: helloworld
+  namespace: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+spec:
+  clusterIP: 1.2.3.4
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: helloworld    
+---
+apiVersion: security.istio.io/v1
path: internal/kgateway/extensions2/plugins/waypoint/testdata/input/authz-ns.yaml
user: login: stevenctl
body: nit: add comments about the expected output here in the input
created_at: 2025-03-26T16:51:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+---
+# The Namespace capture should apply to all the Services in the namespace
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: example-waypoint
+  namespace: infra
+spec:
+  gatewayClassName: kgateway-waypoint
+  listeners:
+  - name: proxy
+    port: 15088
+    protocol: istio.io/PROXY
+---
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-a
+  namespace: infra
+spec:
+  clusterIP: 1.1.1.1
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-b
+  namespace: infra
+spec:
+  clusterIP: 2.2.2.2
+  ports:
+  - port: 9000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: helloworld
+  namespace: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+spec:
+  clusterIP: 1.2.3.4
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: helloworld    
+---
+apiVersion: security.istio.io/v1
+kind: AuthorizationPolicy
+metadata:
+  name: helloworld-authz-svc
+  namespace: infra
+spec:
+  action: DENY
+  rules:
+  - to:
+    - operation:
+        methods:
+        - GET
+  targetRefs:
path: internal/kgateway/extensions2/plugins/waypoint/testdata/input/authz-ns.yaml
user: login: stevenctl
body: this test says we're applying authz policy to the Namespace but this is just doing it to 2 services. 

should this target the waypoint and maybe be `authz-gw`? 
created_at: 2025-03-26T16:51:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+---
+# The Namespace capture should apply to all the Services in the namespace
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: example-waypoint
+  namespace: infra
+spec:
+  gatewayClassName: kgateway-waypoint
+  listeners:
+  - name: proxy
+    port: 15088
+    protocol: istio.io/PROXY
+---
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-a
+  namespace: infra
+spec:
+  clusterIP: 1.1.1.1
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-b
+  namespace: infra
+spec:
+  clusterIP: 2.2.2.2
+  ports:
+  - port: 9000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: helloworld
+  namespace: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+spec:
+  clusterIP: 1.2.3.4
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: helloworld    
+---
+apiVersion: security.istio.io/v1
+kind: AuthorizationPolicy
+metadata:
+  name: helloworld-authz-svc
+  namespace: infra
+spec:
+  action: DENY
+  rules:
+  - to:
+    - operation:
+        methods:
+        - GET
+  targetRefs:
path: internal/kgateway/extensions2/plugins/waypoint/testdata/input/authz-ns.yaml
user: login: stevenctl
body: I know we have a "global authz" feature in istio, but that targets the `GatewayClass` but unfortunately that hardcodes the istio class name; we'll need to adjust istio code or fork the `ListAuthorizationPolicies` part to support this (we might want to do that anyway as part of optimizing within krt)
created_at: 2025-03-26T16:53:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: I think maybe we can read this from an env with `istio-system` as a default? Not sure if there are other places that currently care about this but there will be in the future. 
created_at: 2025-03-26T16:54:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: Or from `settings`
created_at: 2025-03-26T16:54:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
+	policiesMap := model.AuthorizationPolicies{
+		NamespaceToPolicies: map[string][]model.AuthorizationPolicy{},
+		RootNamespace:       rootNamespace,
+	}
+
+	for _, policy := range policies {
+		convertedSpec := crdclient.TranslateObject(policy, gvk.AuthorizationPolicy, "").Spec.(*authpb.AuthorizationPolicy)
+		convertedPolicy := model.AuthorizationPolicy{
+			Name:        policy.Name,
+			Namespace:   policy.Namespace,
+			Annotations: map[string]string{},
+			Spec:        convertedSpec,
+		}
+		policiesMap.NamespaceToPolicies[policy.Namespace] = append(policiesMap.NamespaceToPolicies[policy.Namespace], convertedPolicy)
+	}
+
+	matcher := model.WorkloadPolicyMatcher{
+		IsWaypoint: true,
+		Services: []model.ServiceInfoForPolicyMatcher{
+			{
+				Name:      svc.GetName(),
+				Namespace: svc.GetNamespace(),
+				Registry:  provider.Kubernetes,
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: This should be `provider.External` if the `svc` is a ServiceEntry. Maybe add a `(s *Service) Provider()` method  in `service_model.go`. 
created_at: 2025-03-26T16:55:12Z
author_association: CONTRIBUTOR
path: internal/kgateway/extensions2/plugins/waypoint/rbac_filters.go
user: login: PetrMc
body: Merged to rbac.go - Thanks!
created_at: 2025-03-27T04:10:50Z
author_association: MEMBER
diff_hunk: @@ -37,6 +41,9 @@ type WaypointQueries interface {
 	// GetHTTPRoutesForService fetches HTTPRoutes that have the given Service in parentRefs.
 	GetHTTPRoutesForService(kctx krt.HandlerContext, ctx context.Context, svc *Service) []query.RouteInfo
 
+	// GetAuthorizationPolicies gets all AuthorizationPolicy resources in the targetNamespace and rootNamespace.
path: internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_query.go
user: login: PetrMc
body: good catch! Thanks!
created_at: 2025-03-27T04:14:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,39 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+)
+
+// applyHTTPRBACFilters applies RBAC filters to an HTTP filter chain
+func applyHTTPRBACFilters(httpChain *ir.HttpFilterChainIR, httpRBAC []*ir.CustomEnvoyFilter, svc waypointquery.Service) {
+	// Apply RBAC filters regardless of the presence of proxy_protocol_authority
path: internal/kgateway/extensions2/plugins/waypoint/rbac_filters.go
user: login: PetrMc
body: thanks there is already comment where the function is being called.
created_at: 2025-03-27T04:18:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: PetrMc
body: no follow up needed.
created_at: 2025-03-27T04:18:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,35 @@
+package waypointquery
+
+import (
+	"context"
+
+	istiosecurity "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pkg/kube/krt"
+)
+
+func (w *waypointQueries) GetAuthorizationPolicies(kctx krt.HandlerContext, ctx context.Context, targetNamespace, rootNamespace string) []*istiosecurity.AuthorizationPolicy {
path: internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_authz.go
user: login: PetrMc
body: maybe i'll keep this comment and open a new issue to follow up after 2.0 release.
created_at: 2025-03-27T04:20:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,88 @@
+---
+# The Namespace capture should apply to all the Services in the namespace
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: example-waypoint
+  namespace: infra
+spec:
+  gatewayClassName: kgateway-waypoint
+  listeners:
+  - name: proxy
+    port: 15088
+    protocol: istio.io/PROXY
+---
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-a
+  namespace: infra
+spec:
+  clusterIP: 1.1.1.1
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-b
+  namespace: infra
+spec:
+  clusterIP: 2.2.2.2
+  ports:
+  - port: 9000
+    name: http
+  selector:
+    app: svc-b
+---
+# we should get a filter chain with a default virtualhost that just
+# sends traffic to the corresponding `kube` Service backend
+apiVersion: v1
+kind: Service
+metadata:
+  name: helloworld
+  namespace: infra
+  labels:
+    istio.io/use-waypoint: example-waypoint
+spec:
+  clusterIP: 1.2.3.4
+  ports:
+  - port: 5000
+    name: http
+  selector:
+    app: helloworld    
+---
+apiVersion: security.istio.io/v1
+kind: AuthorizationPolicy
+metadata:
+  name: helloworld-authz-svc
+  namespace: infra
+spec:
+  action: DENY
+  rules:
+  - to:
+    - operation:
+        methods:
+        - GET
+  targetRefs:
path: internal/kgateway/extensions2/plugins/waypoint/testdata/input/authz-ns.yaml
user: login: PetrMc
body: sorry it was my misunderstanding of test definitions. I read it ns - Ambient enabled (labeled) at the namespace level and service enabled/labeled per service. I'll keep one test for now then. As another one will keep it open as part of the enchancment effort after this PR.
created_at: 2025-03-27T04:26:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: PetrMc
body: changed it to a varable and added to the settings, defaulting to istio-system.
created_at: 2025-03-27T05:09:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: PetrMc
body: https://github.com/kgateway-dev/kgateway/blob/2914f7ad58922c98d9b45d4a9b8ce4fb90eabf4b/internal/kgateway/extensions2/settings/settings.go#L21
created_at: 2025-03-27T06:22:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
+	policiesMap := model.AuthorizationPolicies{
+		NamespaceToPolicies: map[string][]model.AuthorizationPolicy{},
+		RootNamespace:       rootNamespace,
+	}
+
+	for _, policy := range policies {
+		convertedSpec := crdclient.TranslateObject(policy, gvk.AuthorizationPolicy, "").Spec.(*authpb.AuthorizationPolicy)
+		convertedPolicy := model.AuthorizationPolicy{
+			Name:        policy.Name,
+			Namespace:   policy.Namespace,
+			Annotations: map[string]string{},
+			Spec:        convertedSpec,
+		}
+		policiesMap.NamespaceToPolicies[policy.Namespace] = append(policiesMap.NamespaceToPolicies[policy.Namespace], convertedPolicy)
+	}
+
+	matcher := model.WorkloadPolicyMatcher{
+		IsWaypoint: true,
+		Services: []model.ServiceInfoForPolicyMatcher{
+			{
+				Name:      svc.GetName(),
+				Namespace: svc.GetNamespace(),
+				Registry:  provider.Kubernetes,
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: PetrMc
body: PTAL - added the function https://github.com/PetrMc/kgateway/blob/2914f7ad58922c98d9b45d4a9b8ce4fb90eabf4b/internal/kgateway/extensions2/plugins/waypoint/waypointquery/service_model.go#L142
created_at: 2025-03-27T06:28:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,39 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+)
+
+// applyHTTPRBACFilters applies RBAC filters to an HTTP filter chain
+func applyHTTPRBACFilters(httpChain *ir.HttpFilterChainIR, httpRBAC []*ir.CustomEnvoyFilter, svc waypointquery.Service) {
+	// Apply RBAC filters regardless of the presence of proxy_protocol_authority
+	if len(httpRBAC) > 0 {
+		// Initialize CustomHTTPFilters if it's nil
+		if httpChain.CustomHTTPFilters == nil {
+			httpChain.CustomHTTPFilters = []ir.CustomEnvoyFilter{}
path: internal/kgateway/extensions2/plugins/waypoint/rbac_filters.go
user: login: PetrMc
body: good point - Thank you! it sounds like it's ok with you to keep it as is for now but will try to use `make()` more often in future
created_at: 2025-03-27T06:34:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
+	policiesMap := model.AuthorizationPolicies{
+		NamespaceToPolicies: map[string][]model.AuthorizationPolicy{},
+		RootNamespace:       rootNamespace,
+	}
+
+	for _, policy := range policies {
+		convertedSpec := crdclient.TranslateObject(policy, gvk.AuthorizationPolicy, "").Spec.(*authpb.AuthorizationPolicy)
+		convertedPolicy := model.AuthorizationPolicy{
+			Name:        policy.Name,
+			Namespace:   policy.Namespace,
+			Annotations: map[string]string{},
+			Spec:        convertedSpec,
+		}
+		policiesMap.NamespaceToPolicies[policy.Namespace] = append(policiesMap.NamespaceToPolicies[policy.Namespace], convertedPolicy)
+	}
+
+	matcher := model.WorkloadPolicyMatcher{
+		IsWaypoint: true,
+		Services: []model.ServiceInfoForPolicyMatcher{
+			{
+				Name:      svc.GetName(),
+				Namespace: svc.GetNamespace(),
+				Registry:  provider.Kubernetes,
+			},
+		},
+		WorkloadNamespace: gatewayNamespace,
+		WorkloadLabels: map[string]string{
+			label.IoK8sNetworkingGatewayGatewayName.Name: gatewayName,
+		},
+	}
+
+	// Call the function
+	policyResult := policiesMap.ListAuthorizationPolicies(matcher)
+
+	if len(policyResult.Deny) == 0 && len(policyResult.Allow) == 0 &&
+		len(policyResult.Audit) == 0 && len(policyResult.Custom) == 0 {
+		return nil
+	}
+
+	trustBundle := trustdomain.NewBundle("cluster.local", nil)
+	builder := builder.New(trustBundle, nil, policyResult, builder.Option{
+		IsCustomBuilder: false,
+		UseFilterState:  true,
+	})
+
+	return builder
+}
+
+func CustomNetworkFilters(
+	extraFilters []*listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) []*ir.CustomEnvoyFilter {
+	customFilters := make([]*ir.CustomEnvoyFilter, 0, len(extraFilters))
+	for _, f := range extraFilters {
+		customFilters = append(customFilters, CustomNetworkFilter(f, stage, predicate))
+	}
+	return customFilters
+}
+
+func CustomNetworkFilter(
+	f *listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) *ir.CustomEnvoyFilter {
+	config := f.GetTypedConfig()
+	if config == nil {
+		return nil
+	}
+
+	return customFiltersHelper(stage, predicate, f.GetName(), config)
+}
+
+func CustomHTTPFilters(
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: PetrMc
body: i got a bit lost there but probably you're right and it can be some duplications.
created_at: 2025-03-27T06:35:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,35 @@
+package waypointquery
+
+import (
+	"context"
+
+	istiosecurity "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pkg/kube/krt"
+)
+
+func (w *waypointQueries) GetAuthorizationPolicies(kctx krt.HandlerContext, ctx context.Context, targetNamespace, rootNamespace string) []*istiosecurity.AuthorizationPolicy {
path: internal/kgateway/extensions2/plugins/waypoint/waypointquery/waypoint_authz.go
user: login: lgadban
body: I created issue just so we don't lose sight of it: https://github.com/kgateway-dev/kgateway/issues/10934

great call!
created_at: 2025-03-27T14:30:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,219 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/settings"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+var (
+	// RootNamespace is the namespace where Istio control plane components are installed.
+	// It is set during initialization via SetRootNamespace() which reads from settings.IstioNamespace.
+	// The default value is "istio-system" if not configured.
+	RootNamespace = "istio-system"
+)
+
+// SetRootNamespace sets the RootNamespace from settings.
+// This should be called during initialization.
+func SetRootNamespace(s *settings.Settings) {
+	if s != nil {
+		RootNamespace = s.IstioNamespace
+	}
+}
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
+	policiesMap := model.AuthorizationPolicies{
+		NamespaceToPolicies: map[string][]model.AuthorizationPolicy{},
+		RootNamespace:       rootNamespace,
+	}
+
+	for _, policy := range policies {
+		convertedSpec := crdclient.TranslateObject(policy, gvk.AuthorizationPolicy, "").Spec.(*authpb.AuthorizationPolicy)
+		convertedPolicy := model.AuthorizationPolicy{
+			Name:        policy.Name,
+			Namespace:   policy.Namespace,
+			Annotations: map[string]string{},
+			Spec:        convertedSpec,
+		}
+		policiesMap.NamespaceToPolicies[policy.Namespace] = append(policiesMap.NamespaceToPolicies[policy.Namespace], convertedPolicy)
+	}
+
+	matcher := model.WorkloadPolicyMatcher{
+		IsWaypoint: true,
+		Services: []model.ServiceInfoForPolicyMatcher{
+			{
+				Name:      svc.GetName(),
+				Namespace: svc.GetNamespace(),
+				Registry:  svc.Provider(),
+			},
+		},
+		WorkloadNamespace: gatewayNamespace,
+		WorkloadLabels: map[string]string{
+			label.IoK8sNetworkingGatewayGatewayName.Name: gatewayName,
+		},
+	}
+
+	// Call the function
+	policyResult := policiesMap.ListAuthorizationPolicies(matcher)
+
+	if len(policyResult.Deny) == 0 && len(policyResult.Allow) == 0 &&
+		len(policyResult.Audit) == 0 && len(policyResult.Custom) == 0 {
+		return nil
+	}
+
+	trustBundle := trustdomain.NewBundle("cluster.local", nil)
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: We should probably put this `cluster.local` in a constant named defaultTrustDomain with a TODO on setting up a way to control trustDomain and trustDomainAliases. 
created_at: 2025-03-27T16:17:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+package waypoint
+
+import (
+	"google.golang.org/protobuf/types/known/anypb"
+	"istio.io/api/label"
+	authpb "istio.io/api/security/v1"
+	authcr "istio.io/client-go/pkg/apis/security/v1"
+	"istio.io/istio/pilot/pkg/config/kube/crdclient"
+	"istio.io/istio/pilot/pkg/model"
+	"istio.io/istio/pilot/pkg/security/authz/builder"
+	"istio.io/istio/pilot/pkg/security/trustdomain"
+	"istio.io/istio/pilot/pkg/serviceregistry/provider"
+	"istio.io/istio/pkg/config/schema/gvk"
+	gwapi "sigs.k8s.io/gateway-api/apis/v1"
+
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/extensions2/plugins/waypoint/waypointquery"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/filters"
+
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/ir"
+	"github.com/kgateway-dev/kgateway/v2/internal/kgateway/plugins"
+)
+
+const (
+	// TODO RootNamespace should be equiv to what istio sees as root ns
+	RootNamespace = "istio-system"
+)
+
+// BuildRBACForService gives three lists of filters:
+// tcpRBAC - only used in tcp chains (using this on an HTTP chain could cause improper DENY)
+// httpRBAC - only used in http chains
+// that passes id from metadata to filter state (see ProxyProtocolTLVAuthorityNetworkFilter)
+func BuildRBACForService(
+	authzPolicies []*authcr.AuthorizationPolicy,
+	gw *gwapi.Gateway,
+	svc *waypointquery.Service,
+) (
+	tcpRBAC []*ir.CustomEnvoyFilter,
+	httpRBAC []*ir.CustomEnvoyFilter,
+) {
+	authzBuilder := getAuthzBuilder(authzPolicies, gw.Name, gw.Namespace, RootNamespace, svc)
+	if authzBuilder != nil {
+		const stage = filters.FilterStage_AuthZStage
+		const predicate = filters.FilterStage_After
+
+		tcpFilters := authzBuilder.BuildTCP()
+		httpFilters := authzBuilder.BuildHTTP()
+
+		if len(tcpFilters) > 0 {
+			tcpRBAC = append(tcpRBAC, CustomNetworkFilters(tcpFilters, stage, predicate)...)
+		}
+		if len(httpFilters) > 0 {
+			httpRBAC = CustomHTTPFilters(httpFilters, stage, predicate)
+		}
+	}
+	return tcpRBAC, httpRBAC
+}
+
+// getAuthzBuilder constructs the istio builder.
+// It can be nil if it filters out all the policies.
+// This relies heavily on Istio code so that we can get similar behavior:
+// https://github.com/istio/istio/blob/master/pilot/pkg/model/policyattachment.go
+func getAuthzBuilder(
+	policies []*authcr.AuthorizationPolicy,
+	gatewayName, gatewayNamespace string,
+	rootNamespace string,
+	svc *waypointquery.Service,
+) *builder.Builder {
+	policiesMap := model.AuthorizationPolicies{
+		NamespaceToPolicies: map[string][]model.AuthorizationPolicy{},
+		RootNamespace:       rootNamespace,
+	}
+
+	for _, policy := range policies {
+		convertedSpec := crdclient.TranslateObject(policy, gvk.AuthorizationPolicy, "").Spec.(*authpb.AuthorizationPolicy)
+		convertedPolicy := model.AuthorizationPolicy{
+			Name:        policy.Name,
+			Namespace:   policy.Namespace,
+			Annotations: map[string]string{},
+			Spec:        convertedSpec,
+		}
+		policiesMap.NamespaceToPolicies[policy.Namespace] = append(policiesMap.NamespaceToPolicies[policy.Namespace], convertedPolicy)
+	}
+
+	matcher := model.WorkloadPolicyMatcher{
+		IsWaypoint: true,
+		Services: []model.ServiceInfoForPolicyMatcher{
+			{
+				Name:      svc.GetName(),
+				Namespace: svc.GetNamespace(),
+				Registry:  provider.Kubernetes,
+			},
+		},
+		WorkloadNamespace: gatewayNamespace,
+		WorkloadLabels: map[string]string{
+			label.IoK8sNetworkingGatewayGatewayName.Name: gatewayName,
+		},
+	}
+
+	// Call the function
+	policyResult := policiesMap.ListAuthorizationPolicies(matcher)
+
+	if len(policyResult.Deny) == 0 && len(policyResult.Allow) == 0 &&
+		len(policyResult.Audit) == 0 && len(policyResult.Custom) == 0 {
+		return nil
+	}
+
+	trustBundle := trustdomain.NewBundle("cluster.local", nil)
+	builder := builder.New(trustBundle, nil, policyResult, builder.Option{
+		IsCustomBuilder: false,
+		UseFilterState:  true,
+	})
+
+	return builder
+}
+
+func CustomNetworkFilters(
+	extraFilters []*listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) []*ir.CustomEnvoyFilter {
+	customFilters := make([]*ir.CustomEnvoyFilter, 0, len(extraFilters))
+	for _, f := range extraFilters {
+		customFilters = append(customFilters, CustomNetworkFilter(f, stage, predicate))
+	}
+	return customFilters
+}
+
+func CustomNetworkFilter(
+	f *listenerv3.Filter,
+	stage filters.FilterStage_Stage,
+	predicate filters.FilterStage_Predicate,
+) *ir.CustomEnvoyFilter {
+	config := f.GetTypedConfig()
+	if config == nil {
+		return nil
+	}
+
+	return customFiltersHelper(stage, predicate, f.GetName(), config)
+}
+
+func CustomHTTPFilters(
path: internal/kgateway/extensions2/plugins/waypoint/rbac.go
user: login: stevenctl
body: Looks like it's not duplicated, but the `ir` package would be a good home for these QoL funcs. 
created_at: 2025-03-27T16:19:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,23 @@
+# Please edit the object below. Lines beginning with a '#' will be ignored,
+# and an empty file will abort the edit. If an error occurs while saving this file will be
+# reopened with the relevant failures.
+#
+apiVersion: security.istio.io/v1
+kind: AuthorizationPolicy
+metadata:
+  name: authz-l7-demy-notcurl-svc-b
path: test/kubernetes/e2e/features/waypoint/testdata/authz-l7.yaml
user: login: lgadban
body: `s/demy/deny` ?
created_at: 2025-03-28T00:28:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: PetrMc
created_at: 2025-03-27T06:53:19Z
body: @lgadban - i think failing tests are flakiness. But i don't have permissions to rerun. Please let me know.

Added unit tests per our slack conversation and addressed most of the comments - there are 1-2 minor ones that seem to be ok not to act on and couple major once that we can start working on when the release is out.
@stevenctl and @lgadban - please let me know if there are still some blockers to merge this PR.
author_association: MEMBER
user: login: lgadban
created_at: 2025-03-27T14:32:15Z
body: > i think failing tests are flakiness. But i don't have permissions to rerun. Please let me know.

~~Will kick all failed tests but the conformance failure is a bit concerning as we haven't seen flakiness on this previously~~

I should have looked at the actual failures before saying this, you're right there was some weirdness going on in CI at that time.
We'll see how the new run goes!
author_association: CONTRIBUTOR
user: login: PetrMc
created_at: 2025-03-27T14:59:00Z
body: it's me after all - adjusting kind shell script for my machine and then committing it. Sorry!
author_association: MEMBER
user: login: lgadban
created_at: 2025-03-27T15:08:53Z
body: @PetrMc think you've got some merge problems, the settings_test tests are hard failing it looks like
author_association: CONTRIBUTOR
user: login: PetrMc
created_at: 2025-03-27T18:17:33Z
body: @lgadban can you please have a look and if all good approve. cc @stevenctl 
author_association: MEMBER