Repository: envoyproxy/gateway

Issue:
improvement of `egctl x collect`
- [x] #7157

- [x] #7156

Discussion:

Solution patch:
diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml
index b514eaa4c..37a0b6cb9 100644
--- a/.github/workflows/build_and_test.yaml
+++ b/.github/workflows/build_and_test.yaml
@@ -96,9 +96,6 @@ jobs:
       with:
         fetch-depth: 0  # Need main branch access for benchmark comparison
     - uses: ./tools/github-actions/setup-deps
-
-    - name: Install benchstat
-      run: go install golang.org/x/perf/cmd/benchstat@latest
     - name: Run Benchmark Comparison
       continue-on-error: true
       run: |
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
index a70e5ecdc..5335b05b2 100644
--- a/examples/extension-server/go.mod
+++ b/examples/extension-server/go.mod
@@ -45,7 +45,7 @@ require (
 	github.com/spf13/pflag v1.0.10 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
-	go.yaml.in/yaml/v2 v2.4.2 // indirect
+	go.yaml.in/yaml/v2 v2.4.3 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
 	golang.org/x/mod v0.28.0 // indirect
 	golang.org/x/net v0.46.0 // indirect
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
index 067ffa21d..42f0bd5b1 100644
--- a/examples/extension-server/go.sum
+++ b/examples/extension-server/go.sum
@@ -168,6 +168,7 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
 go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
 go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
 go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
+go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
 go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/internal/cmd/egctl/collect.go b/internal/cmd/egctl/collect.go
index d412ed6ae..a747cbbb8 100644
--- a/internal/cmd/egctl/collect.go
+++ b/internal/cmd/egctl/collect.go
@@ -8,6 +8,7 @@ package egctl
 import (
 	"context"
 	"fmt"
+	"io"
 	"os"
 	"os/signal"
 	"path/filepath"
@@ -28,6 +29,7 @@ type collectOptions struct {
 	outPath               string
 	envoyGatewayNamespace string
 	proxyNamespaces       []string
+	enableSDS             bool
 }
 
 func newCollectCommand() *cobra.Command {
@@ -39,7 +41,7 @@ func newCollectCommand() *cobra.Command {
   egctl experimental collect
 	`,
 		Run: func(c *cobra.Command, args []string) {
-			cmdutil.CheckErr(runCollect(*collectOpts))
+			cmdutil.CheckErr(runCollect(c.OutOrStderr(), *collectOpts))
 		},
 	}
 
@@ -52,11 +54,13 @@ func newCollectCommand() *cobra.Command {
 		"Specify the namespace where the Envoy Gateway controller is installed.")
 	collectCommand.PersistentFlags().StringArrayVarP(&collectOpts.proxyNamespaces, "envoy-proxy-namespaces", "", []string{},
 		"Specify the namespaces where Envoy proxies are running.")
+	collectCommand.PersistentFlags().BoolVarP(&collectOpts.enableSDS, "sds", "", false,
+		"Specify if the SDS configuration will retrieved or not, Default to false")
 
 	return collectCommand
 }
 
-func runCollect(collectOpts collectOptions) error {
+func runCollect(errWriter io.Writer, collectOpts collectOptions) error {
 	cc := options.DefaultConfigFlags.ToRawKubeConfigLoader()
 	restConfig, err := cc.ClientConfig()
 	if err != nil {
@@ -92,15 +96,20 @@ func runCollect(collectOpts collectOptions) error {
 		return fmt.Errorf("create bundle dir: %w", err)
 	}
 
+	collectedNamespaces := []string{collectOpts.envoyGatewayNamespace}
 	proxyNamespaces := sets.New(collectOpts.proxyNamespaces...)
-	opts := tb.CollectOptions{
-		BundlePath:          bundlePath,
-		CollectedNamespaces: []string{collectOpts.envoyGatewayNamespace},
-	}
 	if len(proxyNamespaces) > 0 {
-		opts.CollectedNamespaces = append(opts.CollectedNamespaces, proxyNamespaces.UnsortedList()...)
+		collectedNamespaces = append(collectedNamespaces, proxyNamespaces.UnsortedList()...)
+	}
+
+	result, err := tb.CollectResult(ctx, restConfig,
+		tb.WithBundlePath(bundlePath),
+		tb.WithCollectedNamespaces(collectedNamespaces),
+		tb.WithSDS(collectOpts.enableSDS),
+	)
+	if err != nil {
+		_, _ = fmt.Fprintln(errWriter, "warning: failed to collect all data:", err)
 	}
-	result := tb.CollectResult(ctx, restConfig, opts)
 	return result.ArchiveBundle(bundlePath, fmt.Sprintf("%s.tar.gz", basename))
 }
 
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 1dd7f2d17..d25da994c 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -2041,6 +2041,8 @@ type TCPRoute struct {
 	BackendConnection *BackendConnection `json:"backendConnection,omitempty" yaml:"backendConnection,omitempty"`
 	// DNS is used to configure how DNS resolution is handled for the route
 	DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
+	// Authorization defines the schema for the authorization.
+	Authorization *Authorization `json:"authorization,omitempty" yaml:"authorization,omitempty"`
 }
 
 // TLS holds information for configuring TLS on a listener
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 31f220378..e041c2360 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -3739,6 +3739,11 @@ func (in *TCPRoute) DeepCopyInto(out *TCPRoute) {
 		*out = new(DNS)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Authorization != nil {
+		in, out := &in.Authorization, &out.Authorization
+		*out = new(Authorization)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRoute.
diff --git a/internal/provider/kubernetes/kubernetes.go b/internal/provider/kubernetes/kubernetes.go
index be77a607b..c35058cb5 100644
--- a/internal/provider/kubernetes/kubernetes.go
+++ b/internal/provider/kubernetes/kubernetes.go
@@ -10,7 +10,9 @@ import (
 	"fmt"
 	"time"
 
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
+	discoveryv1 "k8s.io/api/discovery/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/rest"
 	"k8s.io/klog/v2"
@@ -24,6 +26,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+	gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/envoygateway"
@@ -116,9 +119,9 @@ func New(ctx context.Context, restCfg *rest.Config, svrCfg *ec.Server, resources
 	}
 
 	// Limit the cache to only Envoy proxy Pods to reduce memory and sync churn.
-	// ProxyTopologyInjector is the only component that interacts with Pods.
 	if mgrOpts.Cache.ByObject == nil {
 		mgrOpts.Cache.ByObject = map[client.Object]cache.ByObject{
+			// Disable deepcopy for read only resources
 			&corev1.Secret{}: {
 				UnsafeDisableDeepCopy: ptr.To(true),
 			},
@@ -128,12 +131,25 @@ func New(ctx context.Context, restCfg *rest.Config, svrCfg *ec.Server, resources
 			&corev1.Service{}: {
 				UnsafeDisableDeepCopy: ptr.To(true),
 			},
-			&corev1.ConfigMap{}: {
+			&discoveryv1.EndpointSlice{}: {
+				UnsafeDisableDeepCopy: ptr.To(true),
+			},
+			&appsv1.Deployment{}: {
+				UnsafeDisableDeepCopy: ptr.To(true),
+			},
+			&corev1.Node{}: {
+				UnsafeDisableDeepCopy: ptr.To(true),
+			},
+			&gwapiv1b1.ReferenceGrant{}: {
+				UnsafeDisableDeepCopy: ptr.To(true),
+			},
+			&appsv1.DaemonSet{}: {
 				UnsafeDisableDeepCopy: ptr.To(true),
 			},
 		}
 	}
 
+	// ProxyTopologyInjector is the only component that interacts with Pods.
 	mgrOpts.Cache.ByObject[&corev1.Pod{}] = cache.ByObject{
 		Label: labels.SelectorFromSet(proxy.EnvoyAppLabel()),
 	}
diff --git a/internal/troubleshoot/collect.go b/internal/troubleshoot/collect.go
index 3a7f21c40..a2bf7575b 100644
--- a/internal/troubleshoot/collect.go
+++ b/internal/troubleshoot/collect.go
@@ -7,23 +7,75 @@ package troubleshoot
 
 import (
 	"context"
+	"errors"
 	"fmt"
 
 	troubleshootv1b2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
 	tbcollect "github.com/replicatedhq/troubleshoot/pkg/collect"
+	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/rest"
 
 	"github.com/envoyproxy/gateway/internal/troubleshoot/collect"
 )
 
+type CollectorType string
+
+const (
+	CollectorTypeEnvoyGatewayResource CollectorType = "EnvoyGatewayResource"
+	CollectorTypePrometheusMetrics    CollectorType = "PrometheusMetrics"
+	CollectorTypePodLogs              CollectorType = "PodLogs"
+	CollectorTypeConfigDump           CollectorType = "ConfigDump"
+)
+
 type CollectOptions struct {
-	CollectedNamespaces []string
-	BundlePath          string
+	namespaces []string
+	bundlePath string
+	// enableSDS indicates whether to remove the SDS section from the config dump
+	// to avoid collecting sensitive information.
+	// Default to false
+	enableSDS bool
+
+	disabledCollectors sets.Set[CollectorType]
 }
 
-func CollectResult(ctx context.Context, restConfig *rest.Config, opts CollectOptions) tbcollect.CollectorResult {
-	var result tbcollect.CollectorResult
+type CollectOption func(opts *CollectOptions)
+
+func WithBundlePath(path string) CollectOption {
+	return func(opts *CollectOptions) {
+		opts.bundlePath = path
+	}
+}
+
+func WithCollectedNamespaces(ns []string) CollectOption {
+	return func(opts *CollectOptions) {
+		opts.namespaces = ns
+	}
+}
+
+func DisableCollector(collector CollectorType) CollectOption {
+	return func(opts *CollectOptions) {
+		if opts.disabledCollectors == nil {
+			opts.disabledCollectors = sets.New[CollectorType]()
+		}
+		opts.disabledCollectors.Insert(collector)
+	}
+}
+
+func WithSDS(enabled bool) CollectOption {
+	return func(opts *CollectOptions) {
+		opts.enableSDS = enabled
+	}
+}
+
+func CollectResult(ctx context.Context, restConfig *rest.Config, opts ...CollectOption) (tbcollect.CollectorResult, error) {
+	collectorOpts := &CollectOptions{
+		enableSDS: false,
+	}
+	for _, o := range opts {
+		o(collectorOpts)
+	}
 
+	var result tbcollect.CollectorResult
 	progressChan := make(chan interface{})
 	go func() {
 		select {
@@ -38,50 +90,73 @@ func CollectResult(ctx context.Context, restConfig *rest.Config, opts CollectOpt
 		// Collect the custom resources from Gateway API and EG
 		&collect.CustomResource{
 			ClientConfig: restConfig,
-			BundlePath:   opts.BundlePath,
+			BundlePath:   collectorOpts.bundlePath,
 			IncludeGroups: []string{
 				"gateway.envoyproxy.io",
 				"gateway.networking.k8s.io",
 			},
 		},
 	}
-	for _, ns := range opts.CollectedNamespaces {
-		bundlePath := opts.BundlePath
-		collectors = append(collectors,
-			// Collect resources from EnvoyGateway system namespace
-			collect.EnvoyGatewayResource{
-				ClientConfig: restConfig,
-				BundlePath:   bundlePath,
-				Namespace:    ns,
+
+	bundlePath := collectorOpts.bundlePath
+	for _, ns := range collectorOpts.namespaces {
+		collectorList := []struct {
+			cType     CollectorType
+			collector tbcollect.Collector
+		}{
+			{
+				cType: CollectorTypeEnvoyGatewayResource,
+				collector: collect.EnvoyGatewayResource{
+					ClientConfig: restConfig,
+					BundlePath:   bundlePath,
+					Namespace:    ns,
+				},
 			},
-			// Collect logs from EnvoyGateway system namespace
-			&tbcollect.CollectLogs{
-				Collector: &troubleshootv1b2.Logs{
-					Name:      "pod-logs",
-					Namespace: ns,
+			{
+				cType: CollectorTypePodLogs,
+				collector: &tbcollect.CollectLogs{
+					Collector: &troubleshootv1b2.Logs{
+						Name:      "pod-logs",
+						Namespace: ns,
+					},
+					ClientConfig: restConfig,
+					BundlePath:   bundlePath,
+					Context:      ctx,
 				},
-				ClientConfig: restConfig,
-				BundlePath:   bundlePath,
-				Context:      ctx,
 			},
-			// Collect prometheus metrics from EnvoyGateway system namespace
-			collect.PrometheusMetric{
-				BundlePath:   bundlePath,
-				ClientConfig: restConfig,
-				Namespace:    ns,
+			{
+				cType: CollectorTypePrometheusMetrics,
+				collector: collect.PrometheusMetric{
+					BundlePath:   bundlePath,
+					ClientConfig: restConfig,
+					Namespace:    ns,
+				},
+			},
+			{
+				cType: CollectorTypeConfigDump,
+				collector: collect.ConfigDump{
+					BundlePath:   bundlePath,
+					ClientConfig: restConfig,
+					Namespace:    ns,
+					EnableSDS:    collectorOpts.enableSDS,
+				},
 			},
-			// Collect config dump from EnvoyGateway system namespace
-			collect.ConfigDump{
-				BundlePath:   bundlePath,
-				ClientConfig: restConfig,
-				Namespace:    ns,
-			})
+		}
+
+		for _, c := range collectorList {
+			if !collectorOpts.disabledCollectors.Has(c.cType) {
+				collectors = append(collectors, c.collector)
+			}
+		}
 	}
+
 	total := len(collectors)
 	allCollectedData := make(map[string][]byte)
+	errs := make([]error, 0, len(collectors))
 	for i, collector := range collectors {
 		res, err := collector.Collect(progressChan)
 		if err != nil {
+			errs = append(errs, err)
 			progressChan <- fmt.Errorf("failed to run collector: %s: %w", collector.Title(), err)
 			progressChan <- tbcollect.CollectProgress{
 				CurrentName:    collector.Title(),
@@ -97,5 +172,5 @@ func CollectResult(ctx context.Context, restConfig *rest.Config, opts CollectOpt
 	}
 	result = allCollectedData
 
-	return result
+	return result, errors.Join(errs...)
 }
diff --git a/internal/troubleshoot/collect/config_dump.go b/internal/troubleshoot/collect/config_dump.go
index f49942bc4..e7e29152f 100644
--- a/internal/troubleshoot/collect/config_dump.go
+++ b/internal/troubleshoot/collect/config_dump.go
@@ -11,8 +11,11 @@ import (
 	"fmt"
 	"path"
 
+	adminv3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
 	troubleshootv1b2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
 	tbcollect "github.com/replicatedhq/troubleshoot/pkg/collect"
+	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/types/known/anypb"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/kubernetes"
@@ -21,6 +24,10 @@ import (
 	kube "github.com/envoyproxy/gateway/internal/kubernetes"
 )
 
+const (
+	SecretsConfigDumpTypeURL = "type.googleapis.com/envoy.admin.v3.SecretsConfigDump"
+)
+
 var _ tbcollect.Collector = &ConfigDump{}
 
 // ConfigDump defines a collector that dumps the envoy configuration of the proxy pod.
@@ -28,6 +35,8 @@ type ConfigDump struct {
 	BundlePath   string
 	Namespace    string
 	ClientConfig *rest.Config
+
+	EnableSDS bool
 }
 
 func (cd ConfigDump) Title() string {
@@ -82,6 +91,14 @@ func (cd ConfigDump) Collect(_ chan<- interface{}) (tbcollect.CollectorResult, e
 			continue
 		}
 
+		if !cd.EnableSDS {
+			data, err = trimSDS(data)
+			if err != nil {
+				logs = append(logs, fmt.Sprintf("failed to trim SDS for pod %s/%s: %v", pod.Namespace, pod.Name, err))
+				continue
+			}
+		}
+
 		_ = output.SaveResult(cd.BundlePath, path.Join("config-dumps", pod.Namespace, fmt.Sprintf("%s.json", pod.Name)), bytes.NewBuffer(data))
 	}
 	if len(logs) > 0 {
@@ -98,3 +115,31 @@ func configDump(cli kube.CLIClient, nn types.NamespacedName, includeEds bool) ([
 	}
 	return RequestWithPortForwarder(cli, nn, 19000, reqPath)
 }
+
+var marshaller = protojson.MarshalOptions{
+	Indent: "    ", // pretty output
+}
+
+func trimSDS(input []byte) ([]byte, error) {
+	dump := &adminv3.ConfigDump{}
+	if err := protojson.Unmarshal(input, dump); err != nil {
+		return input, err
+	}
+
+	cfgs := make([]*anypb.Any, 0, len(dump.Configs))
+	for _, section := range dump.Configs {
+		if section.TypeUrl == SecretsConfigDumpTypeURL {
+			continue
+		}
+
+		cfgs = append(cfgs, section)
+	}
+
+	dump.Configs = cfgs
+	res, err := marshaller.Marshal(dump)
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
diff --git a/internal/xds/translator/authorization_tcp.go b/internal/xds/translator/authorization_tcp.go
new file mode 100644
index 000000000..1f89cb680
--- /dev/null
+++ b/internal/xds/translator/authorization_tcp.go
@@ -0,0 +1,126 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package translator
+
+import (
+	cncfv3 "github.com/cncf/xds/go/xds/core/v3"
+	matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3"
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	rbacconfigv3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
+	networkrbacv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
+	"github.com/envoyproxy/go-control-plane/pkg/wellknown"
+	"google.golang.org/protobuf/types/known/anypb"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/ir"
+	"github.com/envoyproxy/gateway/internal/utils/proto"
+)
+
+func buildTCPRBACFilter(statPrefix string, authorization *ir.Authorization) (*listenerv3.Filter, error) {
+	if authorization == nil {
+		return nil, nil
+	}
+
+	rbacCfg, err := buildTCPRBACConfig(statPrefix, authorization)
+	if err != nil {
+		return nil, err
+	}
+	if rbacCfg == nil {
+		return nil, nil
+	}
+
+	return toNetworkFilter(wellknown.RoleBasedAccessControl, rbacCfg)
+}
+
+func buildTCPRBACConfig(statPrefix string, authorization *ir.Authorization) (*networkrbacv3.RBAC, error) {
+	allowAction, denyAction, err := buildTCPRBACActions()
+	if err != nil {
+		return nil, err
+	}
+
+	matchers := make([]*matcherv3.Matcher_MatcherList_FieldMatcher, 0, len(authorization.Rules))
+	for _, rule := range authorization.Rules {
+		predicate, err := buildTCPPrincipalPredicate(rule.Principal)
+		if err != nil {
+			return nil, err
+		}
+
+		ruleAction := allowAction
+		if rule.Action == egv1a1.AuthorizationActionDeny {
+			ruleAction = denyAction
+		}
+
+		matchers = append(matchers, &matcherv3.Matcher_MatcherList_FieldMatcher{
+			Predicate: predicate,
+			OnMatch: &matcherv3.Matcher_OnMatch{
+				OnMatch: &matcherv3.Matcher_OnMatch_Action{
+					Action: &cncfv3.TypedExtensionConfig{
+						Name:        rule.Name,
+						TypedConfig: ruleAction,
+					},
+				},
+			},
+		})
+	}
+
+	defaultAction := denyAction
+	if authorization.DefaultAction == egv1a1.AuthorizationActionAllow {
+		defaultAction = allowAction
+	}
+
+	matcher := &matcherv3.Matcher{
+		OnNoMatch: &matcherv3.Matcher_OnMatch{
+			OnMatch: &matcherv3.Matcher_OnMatch_Action{
+				Action: &cncfv3.TypedExtensionConfig{
+					Name:        "default",
+					TypedConfig: defaultAction,
+				},
+			},
+		},
+	}
+	if len(matchers) > 0 {
+		matcher.MatcherType = &matcherv3.Matcher_MatcherList_{
+			MatcherList: &matcherv3.Matcher_MatcherList{
+				Matchers: matchers,
+			},
+		}
+	} else {
+		matcher.MatcherType = nil
+	}
+
+	return &networkrbacv3.RBAC{
+		StatPrefix: statPrefix,
+		Matcher:    matcher,
+	}, nil
+}
+
+func buildTCPRBACActions() (*anypb.Any, *anypb.Any, error) {
+	allowAction, err := proto.ToAnyWithValidation(&rbacconfigv3.Action{
+		Name:   "ALLOW",
+		Action: rbacconfigv3.RBAC_ALLOW,
+	})
+	if err != nil {
+		return nil, nil, err
+	}
+
+	denyAction, err := proto.ToAnyWithValidation(&rbacconfigv3.Action{
+		Name:   "DENY",
+		Action: rbacconfigv3.RBAC_DENY,
+	})
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return allowAction, denyAction, nil
+}
+
+func buildTCPPrincipalPredicate(principal ir.Principal) (*matcherv3.Matcher_MatcherList_Predicate, error) {
+	// only build predicate for CIDR
+	if len(principal.ClientCIDRs) == 0 {
+		return nil, nil
+	}
+	return buildIPPredicate(principal.ClientCIDRs)
+}
diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go
index 3cb1e5f19..ff6042129 100644
--- a/internal/xds/translator/listener.go
+++ b/internal/xds/translator/listener.go
@@ -640,47 +640,19 @@ func (t *Translator) addXdsTCPFilterChain(
 
 	// Append port to the statPrefix.
 	statPrefix = strings.Join([]string{statPrefix, strconv.Itoa(int(xdsListener.Address.GetSocketAddress().GetPortValue()))}, "-")
-	al, error := buildXdsAccessLog(accesslog, ir.ProxyAccessLogTypeRoute)
-	if error != nil {
-		return error
-	}
-	mgr := &tcpv3.TcpProxy{
-		AccessLog:  al,
-		StatPrefix: statPrefix,
-		ClusterSpecifier: &tcpv3.TcpProxy_Cluster{
-			Cluster: clusterName,
-		},
-		HashPolicy: buildTCPProxyHashPolicy(irRoute.LoadBalancer),
-	}
-
-	if timeout != nil && timeout.TCP != nil {
-		if timeout.TCP.IdleTimeout != nil {
-			mgr.IdleTimeout = durationpb.New(timeout.TCP.IdleTimeout.Duration)
-		}
-	}
-
-	var filters []*listenerv3.Filter
-
-	if connection != nil && connection.ConnectionLimit != nil {
-		cl := buildConnectionLimitFilter(statPrefix, connection)
-		if clf, err := toNetworkFilter(networkConnectionLimit, cl); err == nil {
-			filters = append(filters, clf)
-		} else {
-			return err
-		}
-	}
 
-	if mgrf, err := toNetworkFilter(wellknown.TCPProxy, mgr); err == nil {
-		filters = append(filters, mgrf)
-	} else {
+	filterChain, err := buildTCPFilterChain(
+		irRoute,
+		clusterName,
+		statPrefix,
+		accesslog,
+		timeout,
+		connection,
+	)
+	if err != nil {
 		return err
 	}
 
-	filterChain := &listenerv3.FilterChain{
-		Name:    tlsListenerFilterChainName(irRoute),
-		Filters: filters,
-	}
-
 	if isTLSPassthrough {
 		if err := addServerNamesMatch(xdsListener, filterChain, irRoute.TLS.TLSInspectorConfig.SNIs); err != nil {
 			return err
@@ -707,6 +679,61 @@ func (t *Translator) addXdsTCPFilterChain(
 	return nil
 }
 
+func buildTCPFilterChain(
+	irRoute *ir.TCPRoute,
+	clusterName string,
+	statPrefix string,
+	accesslog *ir.AccessLog,
+	timeout *ir.ClientTimeout,
+	connection *ir.ClientConnection,
+) (*listenerv3.FilterChain, error) {
+	var filters []*listenerv3.Filter
+
+	al, err := buildXdsAccessLog(accesslog, ir.ProxyAccessLogTypeRoute)
+	if err != nil {
+		return nil, err
+	}
+
+	if authzFilter, err := buildTCPRBACFilter(statPrefix, irRoute.Authorization); err != nil {
+		return nil, err
+	} else if authzFilter != nil {
+		filters = append(filters, authzFilter)
+	}
+
+	// Connection limit (if configured)
+	if connection != nil && connection.ConnectionLimit != nil {
+		cl := buildConnectionLimitFilter(statPrefix, connection)
+		if clf, err := toNetworkFilter(networkConnectionLimit, cl); err == nil {
+			filters = append(filters, clf)
+		} else {
+			return nil, err
+		}
+	}
+
+	// TCP proxy last
+	mgr := &tcpv3.TcpProxy{
+		AccessLog:  al,
+		StatPrefix: statPrefix,
+		ClusterSpecifier: &tcpv3.TcpProxy_Cluster{
+			Cluster: clusterName,
+		},
+		HashPolicy: buildTCPProxyHashPolicy(irRoute.LoadBalancer),
+	}
+	if timeout != nil && timeout.TCP != nil && timeout.TCP.IdleTimeout != nil {
+		mgr.IdleTimeout = durationpb.New(timeout.TCP.IdleTimeout.Duration)
+	}
+	if mgrf, err := toNetworkFilter(wellknown.TCPProxy, mgr); err == nil {
+		filters = append(filters, mgrf)
+	} else {
+		return nil, err
+	}
+
+	return &listenerv3.FilterChain{
+		Filters: filters,
+		Name:    tlsListenerFilterChainName(irRoute),
+	}, nil
+}
+
 func buildConnectionLimitFilter(statPrefix string, connection *ir.ClientConnection) *connection_limitv3.ConnectionLimit {
 	cl := &connection_limitv3.ConnectionLimit{
 		StatPrefix:     statPrefix,
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index f7bb606ca..b95ceb04e 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -6,6 +6,7 @@ breaking changes: |
   When a Backend resource specifies TLS settings and SNI is not specified or a BackendTLSPolicy is not attached to it, the value of upstream TLS SNI is determined by the HTTP Host header.
   When a Backend resource specifies TLS settings and SNI is not specified or a BackendTLSPolicy is not attached to it, the upstream certificate is validated for DNS SAN matching the SNI value sent.
   When a MirrorPolicy is used, the shadow host suffix is not appended to the mirrored cluster name.
+  When running `egctl experimental collect`, SDS will not be dumped, you can enable it by adding `--sds true`.
 
 # Updates addressing vulnerabilities, security flaws, or compliance requirements.
 security updates: |
diff --git a/tools/go.mod b/tools/go.mod
index 60a0f2a1c..f4f20d806 100644
--- a/tools/go.mod
+++ b/tools/go.mod
@@ -9,6 +9,7 @@ tool (
 	github.com/google/go-jsonnet/cmd/jsonnet
 	github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb
 	github.com/norwoodj/helm-docs/cmd/helm-docs
+	golang.org/x/perf/cmd/benchstat
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc
 	google.golang.org/protobuf/cmd/protoc-gen-go
 	sigs.k8s.io/controller-runtime/tools/setup-envtest
@@ -55,6 +56,7 @@ require (
 	github.com/Masterminds/sprig/v3 v3.3.0 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
+	github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect
 	github.com/alecthomas/chroma/v2 v2.20.0 // indirect
 	github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
 	github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
@@ -310,16 +312,17 @@ require (
 	go.uber.org/zap v1.27.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
-	golang.org/x/crypto v0.41.0 // indirect
-	golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
+	golang.org/x/crypto v0.43.0 // indirect
+	golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect
 	golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect
-	golang.org/x/mod v0.27.0 // indirect
-	golang.org/x/net v0.43.0 // indirect
-	golang.org/x/sync v0.16.0 // indirect
-	golang.org/x/sys v0.35.0 // indirect
-	golang.org/x/term v0.34.0 // indirect
-	golang.org/x/text v0.28.0 // indirect
-	golang.org/x/tools v0.36.0 // indirect
+	golang.org/x/mod v0.28.0 // indirect
+	golang.org/x/net v0.46.0 // indirect
+	golang.org/x/perf v0.0.0-20251008221758-42ba72fec400 // indirect
+	golang.org/x/sync v0.17.0 // indirect
+	golang.org/x/sys v0.37.0 // indirect
+	golang.org/x/term v0.36.0 // indirect
+	golang.org/x/text v0.30.0 // indirect
+	golang.org/x/tools v0.37.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
diff --git a/tools/go.sum b/tools/go.sum
index c0c914d07..d2d578067 100644
--- a/tools/go.sum
+++ b/tools/go.sum
@@ -74,6 +74,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
 github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
+github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 h1:xlwdaKcTNVW4PtpQb8aKA4Pjy0CdJHEqvFbAnvR5m2g=
+github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY=
 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
@@ -722,10 +724,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
-golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
-golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
-golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A=
+golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
 golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b h1:KdrhdYPDUvJTvrDK9gdjfFd6JTk8vA1WJoldYSi0kHo=
@@ -739,8 +741,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
-golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
+golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
+golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -754,8 +756,10 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
-golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
+golang.org/x/perf v0.0.0-20251008221758-42ba72fec400 h1:qHW4W1d6GSybBiOHXtiSWh/sZwqW08PHkzgawo6aMDw=
+golang.org/x/perf v0.0.0-20251008221758-42ba72fec400/go.mod h1:RtF6FPGf5eWM1TzUzBR+Ixwfe0J7E+CmE+X6BBdY2SQ=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -765,8 +769,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
-golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
-golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
+golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -789,16 +793,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
-golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
-golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
-golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
+golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
+golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -807,8 +811,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
-golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -829,8 +833,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
-golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
-golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
+golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
+golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
 golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
 golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
 golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
diff --git a/tools/hack/go-benchmark-compare.sh b/tools/hack/go-benchmark-compare.sh
index d56a7aa1d..07062521b 100755
--- a/tools/hack/go-benchmark-compare.sh
+++ b/tools/hack/go-benchmark-compare.sh
@@ -9,6 +9,7 @@ set -euo pipefail
 REGRESSION_THRESHOLD=${REGRESSION_THRESHOLD:-5}
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+BENCHSTAT=${BENCHSTAT:-go tool -modfile="$REPO_ROOT/tools/go.mod" benchstat}
 
 # Function to display usage
 usage() {
@@ -118,12 +119,6 @@ if ! git rev-parse --git-dir >/dev/null 2>&1; then
     exit 1
 fi
 
-# Verify benchstat is available
-if ! command -v benchstat >/dev/null 2>&1; then
-    log "ERROR: benchstat not found. Install with: go install golang.org/x/perf/cmd/benchstat@latest"
-    exit 1
-fi
-
 log "Starting benchmark comparison with ${REGRESSION_THRESHOLD}% regression threshold"
 
 # Store current state
@@ -157,7 +152,7 @@ fi
 
 # Compare benchmarks using benchstat
 log "Comparing benchmark results..."
-if ! benchstat main-bench.txt pr-bench.txt > comparison.txt 2>&1; then
+if ! $BENCHSTAT main-bench.txt pr-bench.txt > comparison.txt 2>&1; then
     log "WARNING: Benchstat comparison had issues, but continuing..."
 fi
 



Test patch:
diff --git a/internal/infrastructure/kubernetes/proxy/resource_test.go b/internal/infrastructure/kubernetes/proxy/resource_test.go
index 6f8511d60..4bbca074e 100644
--- a/internal/infrastructure/kubernetes/proxy/resource_test.go
+++ b/internal/infrastructure/kubernetes/proxy/resource_test.go
@@ -7,6 +7,7 @@ package proxy
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -65,7 +66,7 @@ func TestExpectedShutdownManagerSecurityContext(t *testing.T) {
 
 func TestResolveProxyImage(t *testing.T) {
 	defaultImage := egv1a1.DefaultEnvoyProxyImage
-	defaultTag := "distroless-dev"
+	defaultTag := strings.Split(defaultImage, ":")[1]
 
 	tests := []struct {
 		name        string
diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-authorization.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-authorization.yaml
new file mode 100644
index 000000000..be542adb5
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-authorization.yaml
@@ -0,0 +1,34 @@
+tcp:
+- name: "tcp-listener-authorization"
+  address: "::"
+  port: 10080
+  routes:
+  - name: "tcp-route-authorization"
+    authorization:
+      defaultAction: Allow
+      rules:
+      - action: Deny
+        name: deny-office
+        principal:
+          clientCIDRs:
+          - cidr: 10.0.0.0/24
+            distinct: false
+            ip: 10.0.0.0
+            isIPv6: false
+            maskLen: 24
+      - action: Allow
+        name: allow-corp
+        principal:
+          clientCIDRs:
+          - cidr: 192.168.100.0/24
+            distinct: false
+            ip: 192.168.100.0
+            isIPv6: false
+            maskLen: 24
+    destination:
+      name: "tcp-route-authorization-dest"
+      settings:
+      - endpoints:
+        - host: "10.2.3.4"
+          port: 50000
+        name: "tcp-route-authorization-dest/backend/0"
diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.clusters.yaml
new file mode 100644
index 000000000..d30c1238d
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.clusters.yaml
@@ -0,0 +1,24 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: tcp-route-authorization-dest
+  ignoreHealthOnHostRemoval: true
+  lbPolicy: LEAST_REQUEST
+  loadBalancingPolicy:
+    policies:
+    - typedExtensionConfig:
+        name: envoy.load_balancing_policies.least_request
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest
+          localityLbConfig:
+            localityWeightedLbConfig: {}
+  name: tcp-route-authorization-dest
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.endpoints.yaml
new file mode 100644
index 000000000..5a4baed61
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: tcp-route-authorization-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 10.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: tcp-route-authorization-dest/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.listeners.yaml
new file mode 100644
index 000000000..323bb9fd5
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.listeners.yaml
@@ -0,0 +1,69 @@
+- address:
+    socketAddress:
+      address: '::'
+      portValue: 10080
+  filterChains:
+  - filters:
+    - name: envoy.filters.network.rbac
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+        matcher:
+          matcherList:
+            matchers:
+            - onMatch:
+                action:
+                  name: deny-office
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
+              predicate:
+                singlePredicate:
+                  customMatch:
+                    name: ip_matcher
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip
+                      cidrRanges:
+                      - addressPrefix: 10.0.0.0
+                        prefixLen: 24
+                      statPrefix: client_ip
+                  input:
+                    name: client_ip
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput
+            - onMatch:
+                action:
+                  name: allow-corp
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    name: ALLOW
+              predicate:
+                singlePredicate:
+                  customMatch:
+                    name: ip_matcher
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip
+                      cidrRanges:
+                      - addressPrefix: 192.168.100.0
+                        prefixLen: 24
+                      statPrefix: client_ip
+                  input:
+                    name: client_ip
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput
+          onNoMatch:
+            action:
+              name: default
+              typedConfig:
+                '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                name: ALLOW
+        statPrefix: tcp-10080
+    - name: envoy.filters.network.tcp_proxy
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+        cluster: tcp-route-authorization-dest
+        statPrefix: tcp-10080
+    name: tcp-route-authorization
+  maxConnectionsToAcceptPerSocketEvent: 1
+  name: tcp-listener-authorization
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.routes.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-authorization.routes.yaml
@@ -0,0 +1 @@
+[]
diff --git a/test/e2e/tests/oidc-backendcluster.go b/test/e2e/tests/oidc-backendcluster.go
index fec73d743..475ac67b3 100644
--- a/test/e2e/tests/oidc-backendcluster.go
+++ b/test/e2e/tests/oidc-backendcluster.go
@@ -23,10 +23,10 @@ func init() {
 var OIDCBackendClusterTest = suite.ConformanceTest{
 	ShortName:   "OIDC with BackendCluster",
 	Description: "Test OIDC authentication",
-	Manifests:   []string{"testdata/oidc-keycloak.yaml", "testdata/oidc-securitypolicy-backendcluster.yaml"},
+	Manifests:   []string{"testdata/oidc-keycloak.yaml"},
 	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
 		t.Run("oidc provider represented by a BackendCluster", func(t *testing.T) {
-			testOIDC(t, suite)
+			testOIDC(t, suite, "testdata/oidc-securitypolicy-backendcluster.yaml")
 		})
 	},
 }
diff --git a/test/e2e/tests/oidc.go b/test/e2e/tests/oidc.go
index 7f1d74fe6..2e7e1de2a 100644
--- a/test/e2e/tests/oidc.go
+++ b/test/e2e/tests/oidc.go
@@ -48,16 +48,22 @@ func init() {
 var OIDCTest = suite.ConformanceTest{
 	ShortName:   "OIDC",
 	Description: "Test OIDC authentication",
-	Manifests:   []string{"testdata/oidc-keycloak.yaml", "testdata/oidc-securitypolicy.yaml"},
+	Manifests:   []string{"testdata/oidc-keycloak.yaml"},
 	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
 		t.Run("oidc provider represented by a URL", func(t *testing.T) {
-			testOIDC(t, suite)
+			testOIDC(t, suite, "testdata/oidc-securitypolicy.yaml")
 		})
 
 		t.Run("oidc bypass", func(t *testing.T) {
 			ns := "gateway-conformance-infra"
+
+			podInitialized := corev1.PodCondition{Type: corev1.PodInitialized, Status: corev1.ConditionTrue}
 			// Wait for the keycloak pod to be configured with the test user and client
-			WaitForPods(t, suite.Client, ns, map[string]string{"app": "keycloak"}, corev1.PodRunning, &PodReady)
+			WaitForPods(t, suite.Client, ns, map[string]string{"job-name": "setup-keycloak"}, corev1.PodSucceeded, &podInitialized)
+
+			// Apply the security policy that configures OIDC authentication
+			suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, "testdata/oidc-securitypolicy.yaml", true)
+
 			routeWithOIDCNN := types.NamespacedName{Name: "http-with-oidc", Namespace: ns}
 			routeWithoutOIDCNN := types.NamespacedName{Name: "http-without-oidc", Namespace: ns}
 			gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
@@ -112,7 +118,7 @@ var OIDCTest = suite.ConformanceTest{
 	},
 }
 
-func testOIDC(t *testing.T, suite *suite.ConformanceTestSuite) {
+func testOIDC(t *testing.T, suite *suite.ConformanceTestSuite, securityPolicyManifest string) {
 	var (
 		testURL   = "http://www.example.com/myapp"
 		logoutURL = "http://www.example.com/myapp/logout"
@@ -121,8 +127,12 @@ func testOIDC(t *testing.T, suite *suite.ConformanceTestSuite) {
 		ns        = "gateway-conformance-infra"
 	)
 
+	podInitialized := corev1.PodCondition{Type: corev1.PodInitialized, Status: corev1.ConditionTrue}
 	// Wait for the keycloak pod to be configured with the test user and client
-	WaitForPods(t, suite.Client, ns, map[string]string{"app": "keycloak"}, corev1.PodRunning, &PodReady)
+	WaitForPods(t, suite.Client, ns, map[string]string{"job-name": "setup-keycloak"}, corev1.PodSucceeded, &podInitialized)
+
+	// Apply the security policy that configures OIDC authentication
+	suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, securityPolicyManifest, true)
 
 	routeNN := types.NamespacedName{Name: route, Namespace: ns}
 	gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
@@ -179,6 +189,7 @@ func testOIDC(t *testing.T, suite *suite.ConformanceTestSuite) {
 					},
 				}
 				require.NoError(t, suite.Client.Delete(context.TODO(), existingSP))
+				suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, securityPolicyManifest, false)
 				SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: sp, Namespace: ns}, suite.ControllerName, ancestorRef)
 				return false, nil
 			}
diff --git a/test/e2e/tests/utils.go b/test/e2e/tests/utils.go
index c514b588d..ab5f5f013 100644
--- a/test/e2e/tests/utils.go
+++ b/test/e2e/tests/utils.go
@@ -623,20 +623,25 @@ type LokiQueryResponse struct {
 // CollectAndDump collects and dumps the cluster data for troubleshooting and log.
 // This function should be call within t.Cleanup.
 func CollectAndDump(t *testing.T, rest *rest.Config) {
-	if os.Getenv("ACTIONS_STEP_DEBUG") != "true" {
-		tlog.Logf(t, "Skipping collecting and dumping cluster data, set ACTIONS_STEP_DEBUG=true to enable it")
-		return
-	}
-
 	dumpedNamespaces := []string{"envoy-gateway-system"}
 	if IsGatewayNamespaceMode() {
 		dumpedNamespaces = append(dumpedNamespaces, ConformanceInfraNamespace)
 	}
 
-	result := tb.CollectResult(context.TODO(), rest, tb.CollectOptions{
-		BundlePath:          "",
-		CollectedNamespaces: dumpedNamespaces,
-	})
+	opts := []tb.CollectOption{
+		tb.WithCollectedNamespaces(dumpedNamespaces),
+	}
+
+	if os.Getenv("ACTIONS_STEP_DEBUG") != "true" {
+		// don't collector metrics, pod logs and config dumps when ACTIONS_STEP_DEBUG is false
+		opts = append(opts,
+			tb.DisableCollector(tb.CollectorTypePrometheusMetrics),
+			tb.DisableCollector(tb.CollectorTypePodLogs),
+			tb.DisableCollector(tb.CollectorTypeConfigDump),
+		)
+	}
+
+	result, _ := tb.CollectResult(t.Context(), rest, opts...)
 	for r, data := range result {
 		tlog.Logf(t, "\nfilename: %s", r)
 		tlog.Logf(t, "\ndata: \n%s", data)