Repository: envoyproxy/gateway
Pull request #5816 (2025-04-25 13:51:12)

enhancement: add api-version for load
**What type of PR is this?**
<!--
Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary
and summary followed by a colon. format `chore/docs/api/feat/fix/refactor/style/test: summary`.
Examples:
* "docs: fix grammar error"
* "feat(translator): add new feature"
* "fix: fix xx bug"
* "chore: change ci & build tools etc"
* "api: add xxx fields in ClientTrafficPolicy"

Before raising a PR, please go through this section of the developer guide, https://gateway.envoyproxy.io/contributions/develop/#raising-a-pr
-->

<!--
NOTE: If your PR contains any API changes (changes under `/api`), we recommend you to separate these API changes into
a new PR, and we will review the API part first. It will save you lots of implementation time if the API get accepted.
-->

**What this PR does / why we need it**:

- Add support for ReferenceGrant in `load`
- Add APIVersion field for each loaded resources

This is needed for #5767

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->

<!--
For any non-trivial changes, you need to provide a brief description of the changes in the release notes.
Please add the description to the release-notes/current.yaml file and include this file in the PR.
-->
Release Notes: Yes/No


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/5816?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `82.05128%` with `14 lines` in your changes missing coverage. Please review.
> Project coverage is 65.21%. Comparing base [(`096cb8d`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/096cb8d83d1418ed06dfb268b67c41343011b058?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`9bf5e82`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/9bf5e820df18b79877f332bd05a4c2573d59626e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 37 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/5816?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [internal/gatewayapi/resource/load.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5816?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fresource%2Fload.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9yZXNvdXJjZS9sb2FkLmdv) | 82.05% | [14 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5816?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #5816      +/-   ##
==========================================
+ Coverage   65.19%   65.21%   +0.01%     
==========================================
  Files         214      219       +5     
  Lines       34321    34918     +597     
==========================================
+ Hits        22377    22773     +396     
- Misses      10591    10761     +170     
- Partials     1353     1384      +31     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/5816?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/.github/workflows/experimental_conformance.yaml b/.github/workflows/experimental_conformance.yaml
index 35174f7b5..2422c945a 100644
--- a/.github/workflows/experimental_conformance.yaml
+++ b/.github/workflows/experimental_conformance.yaml
@@ -19,7 +19,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        version: [ v1.29.10, v1.30.6, v1.31.4, v1.32.0 ]
+        version: [ v1.30.10, v1.31.6, v1.32.3, v1.33.0 ]
     steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
     - uses: ./tools/github-actions/setup-deps
diff --git a/api/v1alpha1/envoygateway_types.go b/api/v1alpha1/envoygateway_types.go
index f7a9f06b3..395cb3510 100644
--- a/api/v1alpha1/envoygateway_types.go
+++ b/api/v1alpha1/envoygateway_types.go
@@ -560,6 +560,12 @@ type ExtensionService struct {
 	//
 	// +optional
 	TLS *ExtensionTLS `json:"tls,omitempty"`
+
+	// Retry defines the retry policy for to use when errors are encountered in communication with
+	// the extension service.
+	//
+	// +optional
+	Retry *ExtensionServiceRetry `json:"retry,omitempty"`
 }
 
 // ExtensionTLS defines the TLS configuration when connecting to an extension service.
@@ -573,6 +579,43 @@ type ExtensionTLS struct {
 	CertificateRef gwapiv1.SecretObjectReference `json:"certificateRef"`
 }
 
+// GRPCStatus defines grpc status codes as defined in https://github.com/grpc/grpc/blob/master/doc/statuscodes.md.
+// +kubebuilder:validation:Enum=CANCELLED;UNKNOWN;INVALID_ARGUMENT;DEADLINE_EXCEEDED;NOT_FOUND;ALREADY_EXISTS;PERMISSION_DENIED;RESOURCE_EXHAUSTED;FAILED_PRECONDITION;ABORTED;OUT_OF_RANGE;UNIMPLEMENTED;INTERNAL;UNAVAILABLE;DATA_LOSS;UNAUTHENTICATED
+type RetryableGRPCStatusCode string
+
+// ExtensionServiceRetry defines the retry policy for to use when errors are encountered in communication with the extension service.
+type ExtensionServiceRetry struct {
+	// MaxAttempts defines the maximum number of retry attempts.
+	// Default: 4
+	//
+	// +optional
+	MaxAttempts *int `json:"maxAttempts,omitempty"`
+
+	// InitialBackoff defines the initial backoff in seconds for retries, details: https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config.
+	// Default: 0.1s
+	//
+	// +optional
+	InitialBackoff *gwapiv1.Duration `json:"initialBackoff,omitempty"`
+
+	// MaxBackoff defines the maximum backoff in seconds for retries.
+	// Default: 1s
+	//
+	// +optional
+	MaxBackoff *gwapiv1.Duration `json:"maxBackoff,omitempty"`
+
+	// BackoffMultiplier defines the multiplier to use for exponential backoff for retries.
+	// Default: 2.0
+	//
+	// +optional
+	BackoffMultiplier *gwapiv1.Fraction `json:"backoffMultiplier,omitempty"`
+
+	// RetryableStatusCodes defines the grpc status code for which retries will be attempted.
+	// Default: [ "UNAVAILABLE" ]
+	//
+	// +optional
+	RetryableStatusCodes []RetryableGRPCStatusCode `json:"RetryableStatusCodes,omitempty"`
+}
+
 // EnvoyGatewayAdmin defines the Envoy Gateway Admin configuration.
 type EnvoyGatewayAdmin struct {
 	// Address defines the address of Envoy Gateway Admin Server.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index f762d45b6..2ada3a8ba 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -2524,6 +2524,11 @@ func (in *ExtensionService) DeepCopyInto(out *ExtensionService) {
 		*out = new(ExtensionTLS)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Retry != nil {
+		in, out := &in.Retry, &out.Retry
+		*out = new(ExtensionServiceRetry)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionService.
@@ -2536,6 +2541,46 @@ func (in *ExtensionService) DeepCopy() *ExtensionService {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ExtensionServiceRetry) DeepCopyInto(out *ExtensionServiceRetry) {
+	*out = *in
+	if in.MaxAttempts != nil {
+		in, out := &in.MaxAttempts, &out.MaxAttempts
+		*out = new(int)
+		**out = **in
+	}
+	if in.InitialBackoff != nil {
+		in, out := &in.InitialBackoff, &out.InitialBackoff
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.MaxBackoff != nil {
+		in, out := &in.MaxBackoff, &out.MaxBackoff
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.BackoffMultiplier != nil {
+		in, out := &in.BackoffMultiplier, &out.BackoffMultiplier
+		*out = new(v1.Fraction)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.RetryableStatusCodes != nil {
+		in, out := &in.RetryableStatusCodes, &out.RetryableStatusCodes
+		*out = make([]RetryableGRPCStatusCode, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionServiceRetry.
+func (in *ExtensionServiceRetry) DeepCopy() *ExtensionServiceRetry {
+	if in == nil {
+		return nil
+	}
+	out := new(ExtensionServiceRetry)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ExtensionTLS) DeepCopyInto(out *ExtensionTLS) {
 	*out = *in
diff --git a/internal/extension/registry/extension_manager.go b/internal/extension/registry/extension_manager.go
index 2350a3bb2..c1929b476 100644
--- a/internal/extension/registry/extension_manager.go
+++ b/internal/extension/registry/extension_manager.go
@@ -13,13 +13,17 @@ import (
 	"math"
 	"net"
 	"strconv"
+	"strings"
+	"time"
 
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/security/advancedtls"
 	"google.golang.org/grpc/test/bufconn"
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/utils/ptr"
 	k8scli "sigs.k8s.io/controller-runtime/pkg/client"
 	k8sclicfg "sigs.k8s.io/controller-runtime/pkg/client/config"
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
@@ -32,16 +36,16 @@ import (
 	"github.com/envoyproxy/gateway/proto/extension"
 )
 
-const grpcServiceConfig = `{
+const grpcServiceConfigTemplate = `{
 "methodConfig": [{
 	"name": [{"service": "envoygateway.extension.EnvoyGatewayExtension"}],
 	"waitForReady": true,
 	"retryPolicy": {
-		"MaxAttempts": 4,
-		"InitialBackoff": "0.1s",
-		"MaxBackoff": "1s",
-		"BackoffMultiplier": 2.0,
-		"RetryableStatusCodes": [ "UNAVAILABLE" ]
+		"MaxAttempts": %d,
+		"InitialBackoff": "%fs",
+		"MaxBackoff": "%fs",
+		"BackoffMultiplier": %f,
+		"RetryableStatusCodes": [ %s ]
 	}
 }]}`
 
@@ -91,10 +95,23 @@ func NewInMemoryManager(cfg egv1a1.ExtensionManager, server extension.EnvoyGatew
 	go func() {
 		_ = baseServer.Serve(lis)
 	}()
-	conn, err := grpc.DialContext(context.Background(), "",
+
+	inMemoryManagerOpts := []grpc.DialOption{
 		grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
 			return lis.Dial()
-		}), grpc.WithTransportCredentials(insecure.NewCredentials()))
+		}),
+		grpc.WithTransportCredentials(insecure.NewCredentials()),
+	}
+
+	if cfg.Service != nil {
+		opts, err := setupGRPCOpts(context.Background(), nil, &cfg, "")
+		if err != nil {
+			return nil, nil, err
+		}
+		inMemoryManagerOpts = append(inMemoryManagerOpts, opts...)
+	}
+
+	conn, err := grpc.DialContext(context.Background(), "", inMemoryManagerOpts...)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -270,7 +287,18 @@ func setupGRPCOpts(ctx context.Context, client k8scli.Client, ext *egv1a1.Extens
 	} else {
 		opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
 	}
-	opts = append(opts, grpc.WithDefaultServiceConfig(grpcServiceConfig))
+
+	serviceConfig, err := buildServiceConfig(ext)
+	if err != nil {
+		return nil, err
+	}
+	opts = append(opts, grpc.WithDefaultServiceConfig(serviceConfig))
+
+	if ext.Service.Retry != nil {
+		maxAttempts := ptr.Deref(ext.Service.Retry.MaxAttempts, 4)
+		opts = append(opts, grpc.WithMaxCallAttempts(maxAttempts))
+	}
+
 	if ext.MaxMessageSize != nil {
 		maxMessageSize, ok := ext.MaxMessageSize.AsInt64()
 		if !ok {
@@ -324,3 +352,92 @@ func getCertPoolFromSecret(ctx context.Context, client k8scli.Client, ext *egv1a
 	}
 	return cp, nil
 }
+
+func fractionOrDefault(fraction *gwapiv1.Fraction, defaultValue float64) float64 {
+	if fraction != nil {
+		numerator := float64(fraction.Numerator)
+		denominator := float64(100)
+		if fraction.Denominator != nil {
+			denominator = float64(*fraction.Denominator)
+		}
+		return numerator / denominator
+	}
+	return defaultValue
+}
+
+var retryStrToCode = map[string]codes.Code{
+	`"CANCELLED"`:           codes.Canceled,
+	`"UNKNOWN"`:             codes.Unknown,
+	`"INVALID_ARGUMENT"`:    codes.InvalidArgument,
+	`"DEADLINE_EXCEEDED"`:   codes.DeadlineExceeded,
+	`"NOT_FOUND"`:           codes.NotFound,
+	`"ALREADY_EXISTS"`:      codes.AlreadyExists,
+	`"PERMISSION_DENIED"`:   codes.PermissionDenied,
+	`"RESOURCE_EXHAUSTED"`:  codes.ResourceExhausted,
+	`"FAILED_PRECONDITION"`: codes.FailedPrecondition,
+	`"ABORTED"`:             codes.Aborted,
+	`"OUT_OF_RANGE"`:        codes.OutOfRange,
+	`"UNIMPLEMENTED"`:       codes.Unimplemented,
+	`"INTERNAL"`:            codes.Internal,
+	`"UNAVAILABLE"`:         codes.Unavailable,
+	`"DATA_LOSS"`:           codes.DataLoss,
+	`"UNAUTHENTICATED"`:     codes.Unauthenticated,
+}
+
+func getRetryableGRPCCode(retryableCodes []egv1a1.RetryableGRPCStatusCode) (string, error) {
+	var quotedCodes []string
+	for _, statusCode := range retryableCodes {
+		quotedCode := strconv.Quote(string(statusCode))
+		_, found := retryStrToCode[quotedCode]
+		if !found {
+			return "", fmt.Errorf("invalid Extension Manager GRPC Retry Status code value %s", statusCode)
+		} else {
+			quotedCodes = append(quotedCodes, quotedCode)
+		}
+	}
+
+	return strings.Join(quotedCodes, ","), nil
+}
+
+func buildServiceConfig(ext *egv1a1.ExtensionManager) (string, error) {
+	const defaultMaxAttempts = 4
+	const defaultBackoffMultiplier = 2.0
+	const defaultRetryableCodes = "UNAVAILABLE"
+
+	defaultInitialBackoff := gwapiv1.Duration("100ms")
+	defaultMaxBackoff := gwapiv1.Duration("1s")
+
+	maxAttempts := defaultMaxAttempts
+	initialBackoff := defaultInitialBackoff
+	maxBackoff := defaultMaxBackoff
+	backoffMultiplier := defaultBackoffMultiplier
+	grpcRetryableStatusCodes := strconv.Quote(defaultRetryableCodes)
+
+	if ext.Service.Retry != nil {
+		maxAttempts = ptr.Deref(ext.Service.Retry.MaxAttempts, defaultMaxAttempts)
+		initialBackoff = ptr.Deref(ext.Service.Retry.InitialBackoff, defaultInitialBackoff)
+		maxBackoff = ptr.Deref(ext.Service.Retry.MaxBackoff, defaultMaxBackoff)
+		backoffMultiplier = fractionOrDefault(ext.Service.Retry.BackoffMultiplier, defaultBackoffMultiplier)
+
+		if len(ext.Service.Retry.RetryableStatusCodes) > 0 {
+			var err error
+			grpcRetryableStatusCodes, err = getRetryableGRPCCode(ext.Service.Retry.RetryableStatusCodes)
+			if err != nil {
+				return "", err
+			}
+		}
+	}
+
+	initialBackoffDuration, err := time.ParseDuration(string(initialBackoff))
+	if err != nil {
+		return "", fmt.Errorf("invalid Extension Manager GRPC Retry Initial Backoff %s", initialBackoff)
+	}
+
+	maxBackoffDuration, err := time.ParseDuration(string(maxBackoff))
+	if err != nil {
+		return "", fmt.Errorf("invalid Extension Manager GRPC Retry Max Backoff %s", maxBackoff)
+	}
+
+	return fmt.Sprintf(grpcServiceConfigTemplate, maxAttempts, initialBackoffDuration.Seconds(), maxBackoffDuration.Seconds(),
+		backoffMultiplier, grpcRetryableStatusCodes), nil
+}
diff --git a/internal/gatewayapi/resource/load.go b/internal/gatewayapi/resource/load.go
index a17684939..47e6367c5 100644
--- a/internal/gatewayapi/resource/load.go
+++ b/internal/gatewayapi/resource/load.go
@@ -22,6 +22,7 @@ import (
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 	gwapiv1a3 "sigs.k8s.io/gateway-api/apis/v1alpha3"
+	gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 	"sigs.k8s.io/yaml"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
@@ -45,9 +46,6 @@ func LoadResourcesFromYAMLBytes(yamlBytes []byte, addMissingResources bool) (*Re
 }
 
 // loadKubernetesYAMLToResources converts a Kubernetes YAML string into GatewayAPI Resources.
-// TODO: add support for kind:
-//   - BackendLPPolicy (gateway.networking.k8s.io/v1alpha2)
-//   - ReferenceGrant (gateway.networking.k8s.io/v1alpha2)
 func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Resources, error) {
 	resources := NewResources()
 	var useDefaultNamespace bool
@@ -92,6 +90,7 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			return err
 		}
 
+		gv := gvk.GroupVersion().String()
 		objType := reflect.TypeOf(kobj)
 		if objType.Kind() != reflect.Ptr {
 			return fmt.Errorf("expected pointer type, but got %s", objType.Kind().String())
@@ -106,7 +105,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			envoyProxy := &egv1a1.EnvoyProxy{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindEnvoyProxy,
+					Kind:       KindEnvoyProxy,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -120,7 +120,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			gatewayClass := &gwapiv1.GatewayClass{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindGatewayClass,
+					Kind:       KindGatewayClass,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name: name,
@@ -137,7 +138,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			gateway := &gwapiv1.Gateway{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindGateway,
+					Kind:       KindGateway,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -150,7 +152,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			tcpRoute := &gwapiv1a2.TCPRoute{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindTCPRoute,
+					Kind:       KindTCPRoute,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -163,7 +166,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			udpRoute := &gwapiv1a2.UDPRoute{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindUDPRoute,
+					Kind:       KindUDPRoute,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -176,7 +180,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			tlsRoute := &gwapiv1a2.TLSRoute{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindTLSRoute,
+					Kind:       KindTLSRoute,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -189,7 +194,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			httpRoute := &gwapiv1.HTTPRoute{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindHTTPRoute,
+					Kind:       KindHTTPRoute,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -202,7 +208,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			grpcRoute := &gwapiv1.GRPCRoute{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindGRPCRoute,
+					Kind:       KindGRPCRoute,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -213,6 +220,10 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			resources.GRPCRoutes = append(resources.GRPCRoutes, grpcRoute)
 		case KindNamespace:
 			namespace := &corev1.Namespace{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       KindNamespace,
+					APIVersion: gv,
+				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name: name,
 				},
@@ -222,6 +233,10 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 		case KindService:
 			typedSpec := spec.Interface()
 			service := &corev1.Service{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       KindService,
+					APIVersion: gv,
+				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
 					Namespace: namespace,
@@ -237,7 +252,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			envoyPatchPolicy := &egv1a1.EnvoyPatchPolicy{
 				TypeMeta: metav1.TypeMeta{
-					Kind: egv1a1.KindEnvoyPatchPolicy,
+					Kind:       egv1a1.KindEnvoyPatchPolicy,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -250,7 +266,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			clientTrafficPolicy := &egv1a1.ClientTrafficPolicy{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindClientTrafficPolicy,
+					Kind:       KindClientTrafficPolicy,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -263,7 +280,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			backendTrafficPolicy := &egv1a1.BackendTrafficPolicy{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindBackendTrafficPolicy,
+					Kind:       KindBackendTrafficPolicy,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -276,7 +294,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			securityPolicy := &egv1a1.SecurityPolicy{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindSecurityPolicy,
+					Kind:       KindSecurityPolicy,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -290,7 +309,7 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			httpRouteFilter := &egv1a1.HTTPRouteFilter{
 				TypeMeta: metav1.TypeMeta{
 					Kind:       KindHTTPRouteFilter,
-					APIVersion: egv1a1.GroupVersion.String(),
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: namespace,
@@ -303,7 +322,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			backend := &egv1a1.Backend{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindBackend,
+					Kind:       KindBackend,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -319,7 +339,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 
 			secret := &corev1.Secret{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindSecret,
+					Kind:       KindSecret,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -334,7 +355,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedData := data.Interface()
 			configMap := &corev1.ConfigMap{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindConfigMap,
+					Kind:       KindConfigMap,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -347,7 +369,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			backendTLSPolicy := &gwapiv1a3.BackendTLSPolicy{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindBackendTLSPolicy,
+					Kind:       KindBackendTLSPolicy,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -360,7 +383,8 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			typedSpec := spec.Interface()
 			envoyExtensionPolicy := &egv1a1.EnvoyExtensionPolicy{
 				TypeMeta: metav1.TypeMeta{
-					Kind: KindEnvoyExtensionPolicy,
+					Kind:       KindEnvoyExtensionPolicy,
+					APIVersion: gv,
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
@@ -369,6 +393,20 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 				Spec: typedSpec.(egv1a1.EnvoyExtensionPolicySpec),
 			}
 			resources.EnvoyExtensionPolicies = append(resources.EnvoyExtensionPolicies, envoyExtensionPolicy)
+		case KindReferenceGrant:
+			typedSpec := spec.Interface()
+			referenceGrant := &gwapiv1b1.ReferenceGrant{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       KindReferenceGrant,
+					APIVersion: gv,
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      name,
+					Namespace: namespace,
+				},
+				Spec: typedSpec.(gwapiv1b1.ReferenceGrantSpec),
+			}
+			resources.ReferenceGrants = append(resources.ReferenceGrants, referenceGrant)
 		}
 
 		return nil
@@ -378,11 +416,16 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 
 	// The namespace will not be treated as the missing resources in order to improve the user experience
 	// when using the file provider, since namespaces are crucial but easily overlooked.
+	corev1gv := corev1.SchemeGroupVersion.String()
 
 	// Add user provided and resource required namespaces.
 	if useDefaultNamespace {
 		if !providedNamespaceMap.Has(config.DefaultNamespace) {
 			namespace := &corev1.Namespace{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       KindNamespace,
+					APIVersion: corev1gv,
+				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultNamespace,
 				},
@@ -397,6 +440,10 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 	for _, ns := range sortedRequiredNamespace {
 		if !providedNamespaceMap.Has(ns) {
 			namespace := &corev1.Namespace{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       KindNamespace,
+					APIVersion: corev1gv,
+				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name: ns,
 				},
@@ -473,6 +520,7 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 func addMissingServices(requiredServices map[string]*corev1.Service, obj interface{}) {
 	var objNamespace string
 	protocol := ir.TCPProtocolType
+	gv := corev1.SchemeGroupVersion.String()
 
 	var refs []gwapiv1.BackendRef
 	switch route := obj.(type) {
@@ -528,6 +576,10 @@ func addMissingServices(requiredServices map[string]*corev1.Service, obj interfa
 		}
 		if service, found := requiredServices[key]; !found {
 			service := &corev1.Service{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       KindService,
+					APIVersion: gv,
+				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      name,
 					Namespace: ns,
@@ -565,7 +617,12 @@ func addDefaultEnvoyProxy(resources *Resources, namespace string) error {
 	if err != nil {
 		return err
 	}
+	gv := egv1a1.GroupVersion
 	ep := &egv1a1.EnvoyProxy{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       KindEnvoyProxy,
+			APIVersion: gv.String(),
+		},
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: namespace,
 			Name:      defaultEnvoyProxyName,
@@ -579,7 +636,7 @@ func addDefaultEnvoyProxy(resources *Resources, namespace string) error {
 	resources.EnvoyProxyForGatewayClass = ep
 	ns := gwapiv1.Namespace(namespace)
 	resources.GatewayClass.Spec.ParametersRef = &gwapiv1.ParametersReference{
-		Group:     gwapiv1.Group(egv1a1.GroupVersion.Group),
+		Group:     gwapiv1.Group(gv.Group),
 		Kind:      KindEnvoyProxy,
 		Name:      defaultEnvoyProxyName,
 		Namespace: &ns,
diff --git a/internal/gatewayapi/resource/supported_kind.go b/internal/gatewayapi/resource/supported_kind.go
index e9d76e66c..293c53bdf 100644
--- a/internal/gatewayapi/resource/supported_kind.go
+++ b/internal/gatewayapi/resource/supported_kind.go
@@ -27,4 +27,5 @@ const (
 	KindServiceImport        = "ServiceImport"
 	KindSecret               = "Secret"
 	KindHTTPRouteFilter      = "HTTPRouteFilter"
+	KindReferenceGrant       = "ReferenceGrant"
 )
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index dcfc343b9..30cdd3f96 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -36,6 +36,7 @@ new features: |
   Added support for using a local JWKS in an inline string or in a ConfigMap to validate JWT tokens in SecurityPolicy
   Added support for logging the status of resources in standalone mode.
   Added support for per-route tracing in BackendTrafficPolicy
+  Added support for configuring retry settings for Extension Service hooks in EnvoyGateway config.
 
 
 bug fixes: |
diff --git a/site/content/en/news/releases/matrix.md b/site/content/en/news/releases/matrix.md
index 7f144e263..e48592381 100644
--- a/site/content/en/news/releases/matrix.md
+++ b/site/content/en/news/releases/matrix.md
@@ -7,7 +7,7 @@ Envoy Gateway relies on the Envoy Proxy and the Gateway API, and runs within a K
 
 | Envoy Gateway version | Envoy Proxy version         | Rate Limit version | Gateway API version | Kubernetes version         |  End of Life |
 |-----------------------|-----------------------------|--------------------|---------------------|----------------------------|--------------|
-| latest                | **dev-latest**              | **master**         | **v1.2.1**          | v1.29, v1.30, v1.31, v1.32 |  n/a         |
+| latest                | **dev-latest**              | **master**         | **v1.2.1**          | v1.30, v1.31, v1.32, v1.33 |  n/a         |
 | v1.3                  | **distroless-v1.33.0**      | **60d8e81b**       | **v1.2.1**          | v1.29, v1.30, v1.31, v1.32 |  2025/07/30  |
 | v1.2                  | **distroless-v1.32.1**      | **28b1629a**       | **v1.2.0**          | v1.28, v1.29, v1.30, v1.31 |  2025/05/06  |
 | v1.1                  | **distroless-v1.31.0**      | **91484c59**       | **v1.1.0**          | v1.27, v1.28, v1.29, v1.30 |  2025/01/22  |
diff --git a/site/content/en/v1.3/tasks/security/secure-gateways.md b/site/content/en/v1.3/tasks/security/secure-gateways.md
index 2c8d50438..9d6844fe1 100644
--- a/site/content/en/v1.3/tasks/security/secure-gateways.md
+++ b/site/content/en/v1.3/tasks/security/secure-gateways.md
@@ -11,7 +11,7 @@ This task uses a self-signed CA, so it should be used for testing and demonstrat
 
 ## Installation
 
-{{< boilerplate prerequisites >}}
+{{< boilerplate prerequisites >}}  
 
 ## TLS Certificates
 
@@ -317,26 +317,88 @@ This section gives a walkthrough to generate RSA and ECDSA derived certificates
 
 ## Prerequisites
 
-Follow the steps from the [Quickstart](../quickstart) to install Envoy Gateway and the example manifest.
-Before proceeding, you should be able to query the example backend using HTTP.
+{{< boilerplate prerequisites >}}  
 
 Follow the steps in the [TLS Certificates](#tls-certificates) section to generate self-signed RSA derived Server certificate and private key, and configure those in the Gateway listener configuration to terminate HTTPS traffic.
 
 ## Pre-checks
 
-While testing in [Cluster without External LoadBalancer Support](#clusters-without-external-loadbalancer-support), we can query the example app through Envoy proxy while enforcing an RSA cipher, as shown below:
+{{< tabpane text=true >}}
+{{% tab header="With External LoadBalancer Support" %}}
+
+Get the External IP of the Gateway:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+Query the example app through Envoy Proxy while enforcing an RSA cipher:
 
+```shell
+curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call should succeed with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
+...
+...
+```
+
+Since the Secret configured at this point is an RSA based Secret, if we enforce the usage of an ECDSA cipher, the call should fail:
+
+```shell
+ curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call above fails with the following message:
+```
+* Added www.example.com:443:127.0.0.1 to DNS cache
+* Hostname www.example.com was found in DNS cache
+*   Trying 127.0.0.1:443...
+* Connected to www.example.com (127.0.0.1) port 443
+* ALPN: curl offers h2,http/1.1
+* Cipher selection: ECDHE-ECDSA-CHACHA20-POLY1305
+* TLSv1.2 (OUT), TLS handshake, Client hello (1):
+*  CAfile: example.com.crt
+*  CApath: none
+* TLSv1.2 (IN), TLS alert, handshake failure (552):
+* OpenSSL/3.0.14: error:0A000410:SSL routines::sslv3 alert handshake failure
+* Closing connection
+```
+
+{{% /tab %}}
+{{% tab header="Without LoadBalancer Support" %}}
+
+Query the example app through Envoy proxy while enforcing an RSA cipher:
 ```shell
 curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 --cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
 ```
 
-Since the Secret configured at this point is an RSA based Secret, if we enforce the usage of an ECDSA cipher, the call should fail as follows
+The command should succeed with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
+...
+```
+
+Since the Secret configured at this point is an RSA based Secret, if we enforce the usage of an ECDSA cipher, the call should fail:
 
 ```shell
-$ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
---cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+ curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
 
+The command above fails with the following message:
+```
 * Added www.example.com:8443:127.0.0.1 to DNS cache
 * Hostname www.example.com was found in DNS cache
 *   Trying 127.0.0.1:8443...
@@ -351,6 +413,9 @@ $ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 * Closing connection 0
 ```
 
+{{% /tab %}}
+{{< /tabpane >}}
+
 Moving forward in the doc, we will be configuring the existing Gateway listener to accept both kinds of ciphers.
 
 ## TLS Certificates
@@ -392,14 +457,59 @@ kubectl get gateway/eg -o yaml
 
 ## Testing
 
-Again, while testing in Cluster without External LoadBalancer Support, we can query the example app through Envoy proxy while enforcing an RSA cipher, which should work as it did before:
+{{< tabpane text=true >}}
+{{% tab header="With External LoadBalancer Support" %}}
+
+Get the External IP of the Gateway:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+Query the example app through Envoy Proxy while enforcing an RSA cipher:
 
+```shell
+curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call should succeed with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
+...
+...
+```
+
+Additionally, querying the example app while enforcing an ECDSA cipher should also work now:
+
+```shell
+ curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call above succeeds with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305
+...
+```
+
+{{% /tab %}}
+{{% tab header="Without LoadBalancer Support" %}}
+
+Query the example app through Envoy proxy while enforcing an RSA cipher:
 ```shell
 curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 --cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
 ```
 
-```shell
+The command should succeed with the following message:
+```
 ...
 * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
 * TLSv1.2 (IN), TLS handshake, Finished (20):
@@ -414,7 +524,8 @@ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 --cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
 ```
 
-```shell
+The command above succeeds with the following message:
+```
 ...
 * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
 * TLSv1.2 (IN), TLS handshake, Finished (20):
@@ -422,14 +533,16 @@ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 ...
 ```
 
+{{% /tab %}}
+{{< /tabpane >}}
+
 # SNI based Certificate selection
 
 This sections gives a walkthrough to generate multiple certificates corresponding to different FQDNs. The same Gateway listener can then be configured to terminate TLS traffic for multiple FQDNs based on the SNI matching.
 
 ## Prerequisites
 
-Follow the steps from the [Quickstart](../quickstart) to install Envoy Gateway and the example manifest.
-Before proceeding, you should be able to query the example backend using HTTP.
+{{< boilerplate prerequisites >}}  
 
 Follow the steps in the [TLS Certificates](#tls-certificates) section to generate self-signed RSA derived Server certificate and private key, and configure those in the Gateway listener configuration to terminate HTTPS traffic.
 
@@ -475,8 +588,25 @@ kubectl patch httproute backend --type=json --patch '
 {{< tabpane text=true >}}
 {{% tab header="With External LoadBalancer Support" %}}
 
-Refer to the steps mentioned earlier under [Testing in clusters with External LoadBalancer Support](#testing)
+Get the External IP of the Gateway:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+Query the example app through Envoy Proxy:
 
+```shell
+curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get -I
+```
+
+Similarly, query the sample app through the same Envoy proxy:
+
+```shell
+curl -v -HHost:www.sample.com --resolve "www.sample.com:443:${GATEWAY_HOST}" \
+--cacert sample.com.crt https://www.sample.com/get -I
+```
 
 {{% /tab %}}
 {{% tab header="Without LoadBalancer Support" %}}
@@ -507,11 +637,11 @@ curl -v -HHost:www.sample.com --resolve "www.sample.com:8443:127.0.0.1" \
 --cacert sample.com.crt https://www.sample.com:8443/get -I
 ```
 
-Since the multiple certificates are configured on the same Gateway listener, Envoy was able to provide the client with appropriate certificate based on the SNI in the client request.
-
 {{% /tab %}}
 {{< /tabpane >}}
 
+Since the multiple certificates are configured on the same Gateway listener, Envoy was able to provide the client with appropriate certificate based on the SNI in the client request.
+
 ## Customize Gateway TLS Parameters
 
 In addition to enablement of TLS with Gateway-API, Envoy Gateway supports customizing TLS parameters.
@@ -564,12 +694,53 @@ spec:
 
 ## Testing TLS Parameters
 
+{{< tabpane text=true >}}
+{{% tab header="With External LoadBalancer Support" %}}
+
+Attempt to connect using an unsupported TLS version:
+
+```shell
+curl -v -HHost:www.sample.com --resolve "www.sample.com:443:${GATEWAY_HOST}" \
+--cacert sample.com.crt --tlsv1.2 --tls-max 1.2 https://www.sample.com/get -I
+```
+
+You should receive the following error:
+```
+[...]
+
+* ALPN: curl offers h2,http/1.1
+* (304) (OUT), TLS handshake, Client hello (1):
+* LibreSSL/3.3.6: error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
+* Closing connection
+curl: (35) LibreSSL/3.3.6: error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
+```
+
+The output shows that the connection fails due to an unsupported TLS protocol version used by the client. Now, connect to the Gateway without specifying a client version, and note that the connection is established with TLSv1.3.
+
+```shell
+curl -v -HHost:www.sample.com --resolve "www.sample.com:443:${GATEWAY_HOST}" \
+--cacert sample.com.crt https://www.sample.com/get -I
+```
+
+The command above should succeed and output the following:
+```
+[...]
+* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
+```
+
+
+{{% /tab %}}
+{{% tab header="Without LoadBalancer Support" %}}
+
 Attempt to connecting using an unsupported TLS version:
 
 ```shell
 curl -v -HHost:www.sample.com --resolve "www.sample.com:8443:127.0.0.1" \
 --cacert sample.com.crt --tlsv1.2 --tls-max 1.2 https://www.sample.com:8443/get -I
+```
 
+You should receive the following error:
+```
 [...]
 
 * ALPN: curl offers h2,http/1.1
@@ -585,12 +756,17 @@ to the Gateway without specifying a client version, and note that the connection
 ```shell
 curl -v -HHost:www.sample.com --resolve "www.sample.com:8443:127.0.0.1" \
 --cacert sample.com.crt https://www.sample.com:8443/get -I
+```
 
+The command above should succeed and output the following:
+```
 [...]
-
 * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
 ```
 
+{{% /tab %}}
+{{< /tabpane >}}
+
 ## Next Steps
 
 Checkout the [Developer Guide](../../../contributions/develop) to get involved in the project.
diff --git a/tools/hack/create-cluster.sh b/tools/hack/create-cluster.sh
index c69034785..b7e8c8c66 100755
--- a/tools/hack/create-cluster.sh
+++ b/tools/hack/create-cluster.sh
@@ -5,7 +5,7 @@ set -euo pipefail
 # Setup default values
 CLUSTER_NAME=${CLUSTER_NAME:-"envoy-gateway"}
 METALLB_VERSION=${METALLB_VERSION:-"v0.13.10"}
-KIND_NODE_TAG=${KIND_NODE_TAG:-"v1.32.0"}
+KIND_NODE_TAG=${KIND_NODE_TAG:-"v1.33.0"}
 NUM_WORKERS=${NUM_WORKERS:-""}
 IP_FAMILY=${IP_FAMILY:-"ipv4"}
 CUSTOM_CNI=${CUSTOM_CNI:-"false"}
@@ -165,4 +165,3 @@ echo "Applying configuration with retries..."
     sleep $RETRY_INTERVAL
     ELAPSED_TIME=$((ELAPSED_TIME + RETRY_INTERVAL))
   done
-
diff --git a/tools/make/kube.mk b/tools/make/kube.mk
index 231958769..ef5311cd2 100644
--- a/tools/make/kube.mk
+++ b/tools/make/kube.mk
@@ -3,6 +3,7 @@
 # - https://github.com/kubernetes-sigs/controller-tools/blob/main/envtest-releases.yaml
 ENVTEST_K8S_VERSION ?= 1.29.4
 # Need run cel validation across multiple versions of k8s
+# TODO: zhaohuabing update kubebuilder assets to 1.33.0 when available
 ENVTEST_K8S_VERSIONS ?= 1.29.4 1.30.3 1.31.0 1.32.0
 
 # GATEWAY_API_VERSION refers to the version of Gateway API CRDs.


Test patch:
diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml
index f5b144884..65f783c14 100644
--- a/.github/workflows/build_and_test.yaml
+++ b/.github/workflows/build_and_test.yaml
@@ -86,14 +86,14 @@ jobs:
       fail-fast: false
       matrix:
         target:
-        - version: v1.29.10
+        - version: v1.30.10
           ipFamily: ipv4
-        - version: v1.30.6
+        - version: v1.31.6
           ipFamily: ipv4
-        - version: v1.31.4
+        - version: v1.32.3
           ipFamily: ipv6  # only run ipv6 test on this version to save time
         # TODO: this's IPv4 first, need a way to test IPv6 first.
-        - version: v1.32.0
+        - version: v1.33.0
           ipFamily: dual  # only run dual test on latest version to save time
     steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
@@ -125,14 +125,14 @@ jobs:
       fail-fast: false
       matrix:
         target:
-        - version: v1.29.10
+        - version: v1.30.10
           ipFamily: ipv4
-        - version: v1.30.6
+        - version: v1.31.6
           ipFamily: ipv4
-        - version: v1.31.4
+        - version: v1.32.3
           ipFamily: ipv6  # only run ipv6 test on this version to save time
         # TODO: this's IPv4 first, need a way to test IPv6 first.
-        - version: v1.32.0
+        - version: v1.33.0
           ipFamily: dual  # only run dual test on latest version to save time
     steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
diff --git a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml
index a4a50d9e8..e9816d345 100644
--- a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml
@@ -1,5 +1,6 @@
 backendTLSPolicies:
-- kind: BackendTLSPolicy
+- apiVersion: gateway.networking.k8s.io/v1alpha3
+  kind: BackendTLSPolicy
   metadata:
     creationTimestamp: null
     name: example-com-tls-policy
@@ -26,7 +27,8 @@ backendTLSPolicies:
         type: Accepted
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
 backends:
-- kind: Backend
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
   metadata:
     creationTimestamp: null
     name: backend
@@ -45,7 +47,8 @@ backends:
       status: "True"
       type: Accepted
 envoyExtensionPolicies:
-- kind: EnvoyExtensionPolicy
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
   metadata:
     creationTimestamp: null
     name: ext-proc-example
@@ -82,6 +85,7 @@ envoyExtensionPolicies:
         type: Accepted
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -96,7 +100,8 @@ gatewayClass:
       status: "True"
       type: Accepted
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -136,7 +141,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/cmd/egctl/testdata/translate/out/default-namespace.all.yaml b/internal/cmd/egctl/testdata/translate/out/default-namespace.all.yaml
index be03058a5..28520ce31 100644
--- a/internal/cmd/egctl/testdata/translate/out/default-namespace.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/default-namespace.all.yaml
@@ -1,5 +1,6 @@
 backends:
-- kind: Backend
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
   metadata:
     creationTimestamp: null
     name: backend
@@ -18,6 +19,7 @@ backends:
       status: "True"
       type: Accepted
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -32,7 +34,8 @@ gatewayClass:
       status: "True"
       type: Accepted
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -72,7 +75,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
index ae9d9a5f5..cb7f65663 100644
--- a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
@@ -1,4 +1,6 @@
 envoyProxyForGatewayClass:
+  apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyProxy
   metadata:
     creationTimestamp: null
     name: default-envoy-proxy
@@ -176,6 +178,7 @@ envoyProxyForGatewayClass:
     logging: {}
   status: {}
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -195,7 +198,8 @@ gatewayClass:
       status: "True"
       type: Accepted
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -350,7 +354,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
 grpcRoutes:
-- kind: GRPCRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: GRPCRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -395,7 +400,8 @@ grpcRoutes:
         name: eg
         sectionName: grpc
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -437,7 +443,8 @@ httpRoutes:
         kind: Gateway
         name: eg
 tcpRoutes:
-- kind: TCPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TCPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -475,7 +482,8 @@ tcpRoutes:
         name: eg
         sectionName: tcp
 tlsRoutes:
-- kind: TLSRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TLSRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -513,7 +521,8 @@ tlsRoutes:
         name: eg
         sectionName: tls-passthrough
 udpRoutes:
-- kind: UDPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: UDPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml
index f7ee04780..d04f71dc7 100644
--- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml
@@ -1,4 +1,5 @@
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -13,7 +14,8 @@ gatewayClass:
       status: "True"
       type: Accepted
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -53,7 +55,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json
index dc69ded7b..5d8d8bf1e 100644
--- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json
+++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json
@@ -1,6 +1,7 @@
 {
   "gatewayClass": {
     "kind": "GatewayClass",
+    "apiVersion": "gateway.networking.k8s.io/v1",
     "metadata": {
       "name": "eg",
       "creationTimestamp": null
@@ -23,6 +24,7 @@
   "gateways": [
     {
       "kind": "Gateway",
+      "apiVersion": "gateway.networking.k8s.io/v1",
       "metadata": {
         "name": "eg",
         "namespace": "envoy-gateway-system",
@@ -89,6 +91,7 @@
   "httpRoutes": [
     {
       "kind": "HTTPRoute",
+      "apiVersion": "gateway.networking.k8s.io/v1",
       "metadata": {
         "name": "backend",
         "namespace": "envoy-gateway-system",
diff --git a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
index 513131355..7fe4d6837 100644
--- a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
@@ -1,4 +1,5 @@
 envoyProxyForGatewayClass:
+  apiVersion: gateway.envoyproxy.io/v1alpha1
   kind: EnvoyProxy
   metadata:
     creationTimestamp: null
@@ -23,6 +24,7 @@ envoyProxyForGatewayClass:
         default: warn
   status: {}
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -42,7 +44,8 @@ gatewayClass:
       status: "False"
       type: Accepted
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -197,7 +200,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
 grpcRoutes:
-- kind: GRPCRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: GRPCRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -242,7 +246,8 @@ grpcRoutes:
         name: eg
         sectionName: grpc
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -284,7 +289,8 @@ httpRoutes:
         kind: Gateway
         name: eg
 tcpRoutes:
-- kind: TCPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TCPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -322,7 +328,8 @@ tcpRoutes:
         name: eg
         sectionName: tcp
 tlsRoutes:
-- kind: TLSRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TLSRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -360,7 +367,8 @@ tlsRoutes:
         name: eg
         sectionName: tls-passthrough
 udpRoutes:
-- kind: UDPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: UDPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml b/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml
index e456e3a23..f0de18e42 100644
--- a/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml
@@ -1,5 +1,6 @@
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -39,7 +40,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml
index 4749934fd..0f56e88d3 100644
--- a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml
@@ -1,4 +1,5 @@
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -13,7 +14,8 @@ gatewayClass:
       status: "True"
       type: Accepted
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -48,7 +50,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: TLSRoute
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
index 182d48f30..c79870233 100644
--- a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
@@ -1,4 +1,5 @@
 envoyProxyForGatewayClass:
+  apiVersion: gateway.envoyproxy.io/v1alpha1
   kind: EnvoyProxy
   metadata:
     creationTimestamp: null
@@ -18,6 +19,7 @@ envoyProxyForGatewayClass:
       type: Kubernetes
   status: {}
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -37,7 +39,8 @@ gatewayClass:
       status: "True"
       type: Accepted
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -192,7 +195,8 @@ gateways:
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
 grpcRoutes:
-- kind: GRPCRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: GRPCRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -237,7 +241,8 @@ grpcRoutes:
         name: eg
         sectionName: grpc
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -279,7 +284,8 @@ httpRoutes:
         kind: Gateway
         name: eg
 tcpRoutes:
-- kind: TCPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TCPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -317,7 +323,8 @@ tcpRoutes:
         name: eg
         sectionName: tcp
 tlsRoutes:
-- kind: TLSRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TLSRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -355,7 +362,8 @@ tlsRoutes:
         name: eg
         sectionName: tls-passthrough
 udpRoutes:
-- kind: UDPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: UDPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/extension/registry/extension_manager_test.go b/internal/extension/registry/extension_manager_test.go
index 252a19928..fc63af64a 100644
--- a/internal/extension/registry/extension_manager_test.go
+++ b/internal/extension/registry/extension_manager_test.go
@@ -13,12 +13,16 @@ import (
 	"math"
 	"net"
 	"os"
+	"reflect"
+	"sync"
 	"testing"
 
 	v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	"github.com/stretchr/testify/require"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/status"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -143,6 +147,32 @@ func Test_setupGRPCOpts(t *testing.T) {
 								Port:     44344,
 							},
 						},
+						Retry: &egv1a1.ExtensionServiceRetry{
+							MaxAttempts:    ptr.To(20),
+							InitialBackoff: ptr.To(gwapiv1.Duration("500ms")),
+							MaxBackoff:     ptr.To(gwapiv1.Duration("5s")),
+							BackoffMultiplier: &gwapiv1.Fraction{
+								Numerator: 50,
+							},
+							RetryableStatusCodes: []egv1a1.RetryableGRPCStatusCode{
+								"CANCELLED",
+								"UNKNOWN",
+								"INVALID_ARGUMENT",
+								"DEADLINE_EXCEEDED",
+								"NOT_FOUND",
+								"ALREADY_EXISTS",
+								"PERMISSION_DENIED",
+								"RESOURCE_EXHAUSTED",
+								"FAILED_PRECONDITION",
+								"ABORTED",
+								"OUT_OF_RANGE",
+								"UNIMPLEMENTED",
+								"INTERNAL",
+								"UNAVAILABLE",
+								"DATA_LOSS",
+								"UNAUTHENTICATED",
+							},
+						},
 					},
 				},
 			},
@@ -253,3 +283,266 @@ func Test_TLS(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, "test-route", response.Route.Name)
 }
+
+func Test_buildServiceConfig(t *testing.T) {
+	type args struct {
+		ext *egv1a1.ExtensionManager
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    string
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+		{
+			name: "default",
+			args: args{
+				ext: &egv1a1.ExtensionManager{
+					Service: &egv1a1.ExtensionService{
+						BackendEndpoint: egv1a1.BackendEndpoint{
+							FQDN: &egv1a1.FQDNEndpoint{
+								Hostname: "foo.bar",
+								Port:     44344,
+							},
+						},
+					},
+				},
+			},
+			want: `{
+"methodConfig": [{
+	"name": [{"service": "envoygateway.extension.EnvoyGatewayExtension"}],
+	"waitForReady": true,
+	"retryPolicy": {
+		"MaxAttempts": 4,
+		"InitialBackoff": "0.100000s",
+		"MaxBackoff": "1.000000s",
+		"BackoffMultiplier": 2.000000,
+		"RetryableStatusCodes": [ "UNAVAILABLE" ]
+	}
+}]}`,
+		},
+		{
+			name: "valid",
+			args: args{
+				ext: &egv1a1.ExtensionManager{
+					Service: &egv1a1.ExtensionService{
+						BackendEndpoint: egv1a1.BackendEndpoint{
+							FQDN: &egv1a1.FQDNEndpoint{
+								Hostname: "foo.bar",
+								Port:     44344,
+							},
+						},
+						Retry: &egv1a1.ExtensionServiceRetry{
+							MaxAttempts:    ptr.To(20),
+							InitialBackoff: ptr.To(gwapiv1.Duration("500ms")),
+							MaxBackoff:     ptr.To(gwapiv1.Duration("5s")),
+							BackoffMultiplier: &gwapiv1.Fraction{
+								Numerator: 50,
+							},
+							RetryableStatusCodes: []egv1a1.RetryableGRPCStatusCode{
+								"CANCELLED",
+								"UNKNOWN",
+								"INVALID_ARGUMENT",
+								"DEADLINE_EXCEEDED",
+								"NOT_FOUND",
+								"ALREADY_EXISTS",
+								"PERMISSION_DENIED",
+								"RESOURCE_EXHAUSTED",
+								"FAILED_PRECONDITION",
+								"ABORTED",
+								"OUT_OF_RANGE",
+								"UNIMPLEMENTED",
+								"INTERNAL",
+								"UNAVAILABLE",
+								"DATA_LOSS",
+								"UNAUTHENTICATED",
+							},
+						},
+					},
+				},
+			},
+			want: `{
+"methodConfig": [{
+	"name": [{"service": "envoygateway.extension.EnvoyGatewayExtension"}],
+	"waitForReady": true,
+	"retryPolicy": {
+		"MaxAttempts": 20,
+		"InitialBackoff": "0.500000s",
+		"MaxBackoff": "5.000000s",
+		"BackoffMultiplier": 0.500000,
+		"RetryableStatusCodes": [ "CANCELLED","UNKNOWN","INVALID_ARGUMENT","DEADLINE_EXCEEDED","NOT_FOUND","ALREADY_EXISTS","PERMISSION_DENIED","RESOURCE_EXHAUSTED","FAILED_PRECONDITION","ABORTED","OUT_OF_RANGE","UNIMPLEMENTED","INTERNAL","UNAVAILABLE","DATA_LOSS","UNAUTHENTICATED" ]
+	}
+}]}`,
+		},
+		{
+			name: "defaults",
+			args: args{
+				ext: &egv1a1.ExtensionManager{
+					Service: &egv1a1.ExtensionService{
+						BackendEndpoint: egv1a1.BackendEndpoint{
+							FQDN: &egv1a1.FQDNEndpoint{
+								Hostname: "foo.bar",
+								Port:     44344,
+							},
+						},
+					},
+				},
+			},
+			want: `{
+"methodConfig": [{
+	"name": [{"service": "envoygateway.extension.EnvoyGatewayExtension"}],
+	"waitForReady": true,
+	"retryPolicy": {
+		"MaxAttempts": 4,
+		"InitialBackoff": "0.100000s",
+		"MaxBackoff": "1.000000s",
+		"BackoffMultiplier": 2.000000,
+		"RetryableStatusCodes": [ "UNAVAILABLE" ]
+	}
+}]}`,
+		},
+		{
+			name: "invalid-code",
+			args: args{
+				ext: &egv1a1.ExtensionManager{
+					Service: &egv1a1.ExtensionService{
+						BackendEndpoint: egv1a1.BackendEndpoint{
+							FQDN: &egv1a1.FQDNEndpoint{
+								Hostname: "foo.bar",
+								Port:     44344,
+							},
+						},
+						Retry: &egv1a1.ExtensionServiceRetry{
+							RetryableStatusCodes: []egv1a1.RetryableGRPCStatusCode{
+								"CANCELLED",
+								"NOTACODE",
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := buildServiceConfig(tt.args.ext)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("buildServiceConfig() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("buildServiceConfig() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+type retryTestServer struct {
+	extension.UnimplementedEnvoyGatewayExtensionServer
+	attempts int
+	mu       sync.Mutex
+}
+
+func (s *retryTestServer) PostRouteModify(ctx context.Context, req *extension.PostRouteModifyRequest) (*extension.PostRouteModifyResponse, error) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.attempts++
+	if s.attempts == 10 {
+		return &extension.PostRouteModifyResponse{
+			Route: req.Route,
+		}, nil
+	} else {
+		return nil, status.Error(codes.Unavailable, "Service is currently unavailable")
+	}
+}
+
+func Test_Integration_RetryPolicy_MaxAttempts(t *testing.T) {
+	type args struct {
+		retryPolicy *egv1a1.ExtensionServiceRetry
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "sufficient retries",
+			args: args{
+				retryPolicy: &egv1a1.ExtensionServiceRetry{
+					MaxAttempts: ptr.To(10),
+					RetryableStatusCodes: []egv1a1.RetryableGRPCStatusCode{
+						"UNAVAILABLE",
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "insufficient retries",
+			args: args{
+				retryPolicy: &egv1a1.ExtensionServiceRetry{
+					MaxAttempts: ptr.To(5),
+					RetryableStatusCodes: []egv1a1.RetryableGRPCStatusCode{
+						"UNAVAILABLE",
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "wrong retry code",
+			args: args{
+				retryPolicy: &egv1a1.ExtensionServiceRetry{
+					MaxAttempts: ptr.To(5),
+					RetryableStatusCodes: []egv1a1.RetryableGRPCStatusCode{
+						"CANCELLED",
+					},
+				},
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			extManager := egv1a1.ExtensionManager{
+				Hooks: &egv1a1.ExtensionHooks{
+					XDSTranslator: &egv1a1.XDSTranslatorHooks{
+						Post: []egv1a1.XDSTranslatorHook{
+							egv1a1.XDSRoute,
+						},
+					},
+				},
+				Service: &egv1a1.ExtensionService{
+					BackendEndpoint: egv1a1.BackendEndpoint{
+						FQDN: &egv1a1.FQDNEndpoint{
+							Hostname: "foo.bar",
+							Port:     44344,
+						},
+					},
+					Retry: tt.args.retryPolicy,
+				},
+			}
+
+			mgr, _, err := NewInMemoryManager(extManager, &retryTestServer{})
+			require.NoError(t, err)
+
+			hook, err := mgr.GetPostXDSHookClient(egv1a1.XDSRoute)
+			require.NoError(t, err)
+			require.NotNil(t, hook)
+
+			_, err = hook.PostRouteModifyHook(
+				&v3.Route{
+					Name: "test-route",
+				}, nil, nil)
+
+			if (err != nil) != tt.wantErr {
+				t.Errorf("PostRouteModifyHook() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+		})
+	}
+}
diff --git a/internal/gatewayapi/resource/testdata/all-resources.in.yaml b/internal/gatewayapi/resource/testdata/all-resources.in.yaml
index f5cba5ff6..68c63c24a 100644
--- a/internal/gatewayapi/resource/testdata/all-resources.in.yaml
+++ b/internal/gatewayapi/resource/testdata/all-resources.in.yaml
@@ -275,3 +275,57 @@ data:
   game.properties: |
     enemy.types=aliens,monsters
     player.maximum-lives=5
+---
+apiVersion: gateway.networking.k8s.io/v1alpha3
+kind: BackendTLSPolicy
+metadata:
+  name: example-tls-policy
+spec:
+  targetRefs:
+    - group: 'gateway.envoyproxy.io'
+      kind: Backend
+      name: backend
+  validation:
+    wellKnownCACertificates: "System"
+    hostname: www.example.com
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyExtensionPolicy
+metadata:
+  name: ext-proc-example
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: backend
+  extProc:
+    - backendRefs:
+        - name: grpc-ext-proc
+          port: 9002
+      processingMode:
+        request: {}
+        response:
+          body: Streamed
+---
+apiVersion: gateway.networking.k8s.io/v1beta1
+kind: ReferenceGrant
+metadata:
+  name: refg-example
+  namespace: default
+spec:
+  from:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      namespace: envoy-gateway
+    - group: gateway.networking.k8s.io
+      kind: TCPRoute
+      namespace: envoy-gateway
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      namespace: envoy-gateway
+    - group: gateway.networking.k8s.io
+      kind: BackendTLSPolicy
+      namespace: default
+  to:
+    - group: ""
+      kind: Service
diff --git a/internal/gatewayapi/resource/testdata/all-resources.out.yaml b/internal/gatewayapi/resource/testdata/all-resources.out.yaml
index 5210fa05c..612090f67 100644
--- a/internal/gatewayapi/resource/testdata/all-resources.out.yaml
+++ b/internal/gatewayapi/resource/testdata/all-resources.out.yaml
@@ -1,5 +1,23 @@
+backendTLSPolicies:
+- apiVersion: gateway.networking.k8s.io/v1alpha3
+  kind: BackendTLSPolicy
+  metadata:
+    creationTimestamp: null
+    name: example-tls-policy
+    namespace: envoy-gateway-system
+  spec:
+    targetRefs:
+    - group: gateway.envoyproxy.io
+      kind: Backend
+      name: backend
+    validation:
+      hostname: www.example.com
+      wellKnownCACertificates: System
+  status:
+    ancestors: null
 backendTrafficPolicies:
-- kind: BackendTrafficPolicy
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
   metadata:
     creationTimestamp: null
     name: cookie-lb-policy
@@ -44,7 +62,8 @@ backendTrafficPolicies:
   status:
     ancestors: null
 backends:
-- kind: Backend
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
   metadata:
     creationTimestamp: null
     name: backend
@@ -57,7 +76,8 @@ backends:
     type: Endpoints
   status: {}
 clientTrafficPolicies:
-- kind: ClientTrafficPolicy
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: ClientTrafficPolicy
   metadata:
     creationTimestamp: null
     name: client-timeout
@@ -73,7 +93,8 @@ clientTrafficPolicies:
   status:
     ancestors: null
 configMaps:
-- data:
+- apiVersion: v1
+  data:
     game.properties: |
       enemy.types=aliens,monsters
       player.maximum-lives=5
@@ -83,8 +104,33 @@ configMaps:
     creationTimestamp: null
     name: configmap
     namespace: default
+envoyExtensionPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: ext-proc-example
+    namespace: envoy-gateway-system
+  spec:
+    extProc:
+    - backendRefs:
+      - group: ""
+        kind: Service
+        name: grpc-ext-proc
+        port: 9002
+      processingMode:
+        request: {}
+        response:
+          body: Streamed
+    targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: backend
+  status:
+    ancestors: null
 envoyPatchPolicies:
-- kind: EnvoyPatchPolicy
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyPatchPolicy
   metadata:
     creationTimestamp: null
     name: ratelimit-patch-policy
@@ -116,6 +162,7 @@ envoyPatchPolicies:
   status:
     ancestors: null
 envoyProxyForGatewayClass:
+  apiVersion: gateway.envoyproxy.io/v1alpha1
   kind: EnvoyProxy
   metadata:
     creationTimestamp: null
@@ -135,6 +182,7 @@ envoyProxyForGatewayClass:
       type: Kubernetes
   status: {}
 gatewayClass:
+  apiVersion: gateway.networking.k8s.io/v1
   kind: GatewayClass
   metadata:
     creationTimestamp: null
@@ -143,7 +191,8 @@ gatewayClass:
     controllerName: gateway.envoyproxy.io/gatewayclass-controller
   status: {}
 gateways:
-- kind: Gateway
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
   metadata:
     creationTimestamp: null
     name: eg
@@ -159,7 +208,8 @@ gateways:
       protocol: HTTP
   status: {}
 grpcRoutes:
-- kind: GRPCRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: GRPCRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -204,7 +254,8 @@ httpFilters:
         type: Inline
       contentType: text/plain
 httpRoutes:
-- kind: HTTPRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -230,23 +281,54 @@ httpRoutes:
   status:
     parents: null
 namespaces:
-- metadata:
+- apiVersion: v1
+  kind: Namespace
+  metadata:
     creationTimestamp: null
     name: envoy-gateway-system
   spec: {}
   status: {}
-- metadata:
+- apiVersion: v1
+  kind: Namespace
+  metadata:
     creationTimestamp: null
     name: default
   spec: {}
   status: {}
-- metadata:
+- apiVersion: v1
+  kind: Namespace
+  metadata:
     creationTimestamp: null
     name: gateway-conformance-infra
   spec: {}
   status: {}
+referenceGrants:
+- apiVersion: gateway.networking.k8s.io/v1beta1
+  kind: ReferenceGrant
+  metadata:
+    creationTimestamp: null
+    name: refg-example
+    namespace: default
+  spec:
+    from:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      namespace: envoy-gateway
+    - group: gateway.networking.k8s.io
+      kind: TCPRoute
+      namespace: envoy-gateway
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      namespace: envoy-gateway
+    - group: gateway.networking.k8s.io
+      kind: BackendTLSPolicy
+      namespace: default
+    to:
+    - group: ""
+      kind: Service
 secrets:
-- data:
+- apiVersion: v1
+  data:
     .secret-file: dmFsdWUtMg0KDQo=
   kind: Secret
   metadata:
@@ -255,21 +337,24 @@ secrets:
     namespace: default
   stringData:
     secret: literal value
-- data:
+- apiVersion: v1
+  data:
     .secret-file: dmFsdWUtMg0KDQo=
   kind: Secret
   metadata:
     creationTimestamp: null
     name: secret-with-data
     namespace: default
-- kind: Secret
+- apiVersion: v1
+  kind: Secret
   metadata:
     creationTimestamp: null
     name: secret-with-string-data
     namespace: default
   stringData:
     secret: literal value
-- data:
+- apiVersion: v1
+  data:
     .secret-file: dmFsdWUtMg0KDQo=
   kind: Secret
   metadata:
@@ -278,7 +363,8 @@ secrets:
     namespace: default
   type: type value
 securityPolicies:
-- kind: SecurityPolicy
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
   metadata:
     creationTimestamp: null
     name: jwt-example
@@ -304,7 +390,9 @@ securityPolicies:
   status:
     ancestors: null
 services:
-- metadata:
+- apiVersion: v1
+  kind: Service
+  metadata:
     creationTimestamp: null
     name: backend
     namespace: default
@@ -321,7 +409,9 @@ services:
       targetPort: 0
   status:
     loadBalancer: {}
-- metadata:
+- apiVersion: v1
+  kind: Service
+  metadata:
     creationTimestamp: null
     name: providedBackend
     namespace: default
@@ -339,7 +429,8 @@ services:
   status:
     loadBalancer: {}
 tcpRoutes:
-- kind: TCPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TCPRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -360,7 +451,8 @@ tcpRoutes:
   status:
     parents: null
 tlsRoutes:
-- kind: TLSRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: TLSRoute
   metadata:
     creationTimestamp: null
     name: backend
@@ -381,7 +473,8 @@ tlsRoutes:
   status:
     parents: null
 udpRoutes:
-- kind: UDPRoute
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: UDPRoute
   metadata:
     creationTimestamp: null
     name: backend
diff --git a/internal/provider/file/testdata/resources.all.yaml b/internal/provider/file/testdata/resources.all.yaml
index 00b695657..989ae8025 100644
--- a/internal/provider/file/testdata/resources.all.yaml
+++ b/internal/provider/file/testdata/resources.all.yaml
@@ -1,5 +1,6 @@
 backends:
 - kind: Backend
+  apiVersion: gateway.envoyproxy.io/v1alpha1
   metadata:
     creationTimestamp: null
     name: backend
@@ -13,6 +14,7 @@ backends:
   status: {}
 gatewayClass:
   kind: GatewayClass
+  apiVersion: gateway.networking.k8s.io/v1
   metadata:
     creationTimestamp: null
     name: eg
@@ -21,6 +23,7 @@ gatewayClass:
   status: {}
 gateways:
 - kind: Gateway
+  apiVersion: gateway.networking.k8s.io/v1
   metadata:
     creationTimestamp: null
     name: eg
@@ -37,6 +40,7 @@ gateways:
   status: {}
 httpRoutes:
 - kind: HTTPRoute
+  apiVersion: gateway.networking.k8s.io/v1
   metadata:
     creationTimestamp: null
     name: backend
@@ -61,7 +65,9 @@ httpRoutes:
   status:
     parents: null
 namespaces:
-- metadata:
+- kind: Namespace
+  apiVersion: v1
+  metadata:
     creationTimestamp: null
     name: envoy-gateway-system
   spec: {}
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index e931d4055..4b121a079 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -1767,6 +1767,25 @@ _Appears in:_
 | `host` | _string_ |  false  |  | Host define the extension service hostname.<br />Deprecated: use the appropriate transport attribute instead (FQDN,IP,Unix) |
 | `port` | _integer_ |  false  | 80 | Port defines the port the extension service is exposed on.<br />Deprecated: use the appropriate transport attribute instead (FQDN,IP,Unix) |
 | `tls` | _[ExtensionTLS](#extensiontls)_ |  false  |  | TLS defines TLS configuration for communication between Envoy Gateway and<br />the extension service. |
+| `retry` | _[ExtensionServiceRetry](#extensionserviceretry)_ |  false  |  | Retry defines the retry policy for to use when errors are encountered in communication with<br />the extension service. |
+
+
+#### ExtensionServiceRetry
+
+
+
+ExtensionServiceRetry defines the retry policy for to use when errors are encountered in communication with the extension service.
+
+_Appears in:_
+- [ExtensionService](#extensionservice)
+
+| Field | Type | Required | Default | Description |
+| ---   | ---  | ---      | ---     | ---         |
+| `maxAttempts` | _integer_ |  false  |  | MaxAttempts defines the maximum number of retry attempts.<br />Default: 4 |
+| `initialBackoff` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ |  false  |  | InitialBackoff defines the initial backoff in seconds for retries, details: https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config.<br />Default: 0.1s |
+| `maxBackoff` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ |  false  |  | MaxBackoff defines the maximum backoff in seconds for retries.<br />Default: 1s |
+| `backoffMultiplier` | _[Fraction](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Fraction)_ |  false  |  | BackoffMultiplier defines the multiplier to use for exponential backoff for retries.<br />Default: 2.0 |
+| `RetryableStatusCodes` | _[RetryableGRPCStatusCode](#retryablegrpcstatuscode) array_ |  false  |  | RetryableStatusCodes defines the grpc status code for which retries will be attempted.<br />Default: [ "UNAVAILABLE" ] |
 
 
 #### ExtensionTLS
@@ -4113,6 +4132,17 @@ _Appears in:_
 | `httpStatusCodes` | _[HTTPStatus](#httpstatus) array_ |  false  |  | HttpStatusCodes specifies the http status codes to be retried.<br />The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. |
 
 
+#### RetryableGRPCStatusCode
+
+_Underlying type:_ _string_
+
+GRPCStatus defines grpc status codes as defined in https://github.com/grpc/grpc/blob/master/doc/statuscodes.md.
+
+_Appears in:_
+- [ExtensionServiceRetry](#extensionserviceretry)
+
+
+
 #### RoutingType
 
 _Underlying type:_ _string_
diff --git a/site/content/en/latest/tasks/security/secure-gateways.md b/site/content/en/latest/tasks/security/secure-gateways.md
index 2c8d50438..9d6844fe1 100644
--- a/site/content/en/latest/tasks/security/secure-gateways.md
+++ b/site/content/en/latest/tasks/security/secure-gateways.md
@@ -11,7 +11,7 @@ This task uses a self-signed CA, so it should be used for testing and demonstrat
 
 ## Installation
 
-{{< boilerplate prerequisites >}}
+{{< boilerplate prerequisites >}}  
 
 ## TLS Certificates
 
@@ -317,26 +317,88 @@ This section gives a walkthrough to generate RSA and ECDSA derived certificates
 
 ## Prerequisites
 
-Follow the steps from the [Quickstart](../quickstart) to install Envoy Gateway and the example manifest.
-Before proceeding, you should be able to query the example backend using HTTP.
+{{< boilerplate prerequisites >}}  
 
 Follow the steps in the [TLS Certificates](#tls-certificates) section to generate self-signed RSA derived Server certificate and private key, and configure those in the Gateway listener configuration to terminate HTTPS traffic.
 
 ## Pre-checks
 
-While testing in [Cluster without External LoadBalancer Support](#clusters-without-external-loadbalancer-support), we can query the example app through Envoy proxy while enforcing an RSA cipher, as shown below:
+{{< tabpane text=true >}}
+{{% tab header="With External LoadBalancer Support" %}}
+
+Get the External IP of the Gateway:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+Query the example app through Envoy Proxy while enforcing an RSA cipher:
 
+```shell
+curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call should succeed with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
+...
+...
+```
+
+Since the Secret configured at this point is an RSA based Secret, if we enforce the usage of an ECDSA cipher, the call should fail:
+
+```shell
+ curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call above fails with the following message:
+```
+* Added www.example.com:443:127.0.0.1 to DNS cache
+* Hostname www.example.com was found in DNS cache
+*   Trying 127.0.0.1:443...
+* Connected to www.example.com (127.0.0.1) port 443
+* ALPN: curl offers h2,http/1.1
+* Cipher selection: ECDHE-ECDSA-CHACHA20-POLY1305
+* TLSv1.2 (OUT), TLS handshake, Client hello (1):
+*  CAfile: example.com.crt
+*  CApath: none
+* TLSv1.2 (IN), TLS alert, handshake failure (552):
+* OpenSSL/3.0.14: error:0A000410:SSL routines::sslv3 alert handshake failure
+* Closing connection
+```
+
+{{% /tab %}}
+{{% tab header="Without LoadBalancer Support" %}}
+
+Query the example app through Envoy proxy while enforcing an RSA cipher:
 ```shell
 curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 --cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
 ```
 
-Since the Secret configured at this point is an RSA based Secret, if we enforce the usage of an ECDSA cipher, the call should fail as follows
+The command should succeed with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
+...
+```
+
+Since the Secret configured at this point is an RSA based Secret, if we enforce the usage of an ECDSA cipher, the call should fail:
 
 ```shell
-$ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
---cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+ curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
 
+The command above fails with the following message:
+```
 * Added www.example.com:8443:127.0.0.1 to DNS cache
 * Hostname www.example.com was found in DNS cache
 *   Trying 127.0.0.1:8443...
@@ -351,6 +413,9 @@ $ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 * Closing connection 0
 ```
 
+{{% /tab %}}
+{{< /tabpane >}}
+
 Moving forward in the doc, we will be configuring the existing Gateway listener to accept both kinds of ciphers.
 
 ## TLS Certificates
@@ -392,14 +457,59 @@ kubectl get gateway/eg -o yaml
 
 ## Testing
 
-Again, while testing in Cluster without External LoadBalancer Support, we can query the example app through Envoy proxy while enforcing an RSA cipher, which should work as it did before:
+{{< tabpane text=true >}}
+{{% tab header="With External LoadBalancer Support" %}}
+
+Get the External IP of the Gateway:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+Query the example app through Envoy Proxy while enforcing an RSA cipher:
 
+```shell
+curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call should succeed with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
+...
+...
+```
+
+Additionally, querying the example app while enforcing an ECDSA cipher should also work now:
+
+```shell
+ curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
+```
+
+The call above succeeds with the following message:
+```
+...
+* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
+* TLSv1.2 (IN), TLS handshake, Finished (20):
+* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305
+...
+```
+
+{{% /tab %}}
+{{% tab header="Without LoadBalancer Support" %}}
+
+Query the example app through Envoy proxy while enforcing an RSA cipher:
 ```shell
 curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 --cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-RSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
 ```
 
-```shell
+The command should succeed with the following message:
+```
 ...
 * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
 * TLSv1.2 (IN), TLS handshake, Finished (20):
@@ -414,7 +524,8 @@ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 --cacert example.com.crt https://www.example.com:8443/get  -Isv --ciphers ECDHE-ECDSA-CHACHA20-POLY1305 --tlsv1.2 --tls-max 1.2
 ```
 
-```shell
+The command above succeeds with the following message:
+```
 ...
 * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
 * TLSv1.2 (IN), TLS handshake, Finished (20):
@@ -422,14 +533,16 @@ curl -v -HHost:www.example.com --resolve "www.example.com:8443:127.0.0.1" \
 ...
 ```
 
+{{% /tab %}}
+{{< /tabpane >}}
+
 # SNI based Certificate selection
 
 This sections gives a walkthrough to generate multiple certificates corresponding to different FQDNs. The same Gateway listener can then be configured to terminate TLS traffic for multiple FQDNs based on the SNI matching.
 
 ## Prerequisites
 
-Follow the steps from the [Quickstart](../quickstart) to install Envoy Gateway and the example manifest.
-Before proceeding, you should be able to query the example backend using HTTP.
+{{< boilerplate prerequisites >}}  
 
 Follow the steps in the [TLS Certificates](#tls-certificates) section to generate self-signed RSA derived Server certificate and private key, and configure those in the Gateway listener configuration to terminate HTTPS traffic.
 
@@ -475,8 +588,25 @@ kubectl patch httproute backend --type=json --patch '
 {{< tabpane text=true >}}
 {{% tab header="With External LoadBalancer Support" %}}
 
-Refer to the steps mentioned earlier under [Testing in clusters with External LoadBalancer Support](#testing)
+Get the External IP of the Gateway:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+Query the example app through Envoy Proxy:
 
+```shell
+curl -v -HHost:www.example.com --resolve "www.example.com:443:${GATEWAY_HOST}" \
+--cacert example.com.crt https://www.example.com/get -I
+```
+
+Similarly, query the sample app through the same Envoy proxy:
+
+```shell
+curl -v -HHost:www.sample.com --resolve "www.sample.com:443:${GATEWAY_HOST}" \
+--cacert sample.com.crt https://www.sample.com/get -I
+```
 
 {{% /tab %}}
 {{% tab header="Without LoadBalancer Support" %}}
@@ -507,11 +637,11 @@ curl -v -HHost:www.sample.com --resolve "www.sample.com:8443:127.0.0.1" \
 --cacert sample.com.crt https://www.sample.com:8443/get -I
 ```
 
-Since the multiple certificates are configured on the same Gateway listener, Envoy was able to provide the client with appropriate certificate based on the SNI in the client request.
-
 {{% /tab %}}
 {{< /tabpane >}}
 
+Since the multiple certificates are configured on the same Gateway listener, Envoy was able to provide the client with appropriate certificate based on the SNI in the client request.
+
 ## Customize Gateway TLS Parameters
 
 In addition to enablement of TLS with Gateway-API, Envoy Gateway supports customizing TLS parameters.
@@ -564,12 +694,53 @@ spec:
 
 ## Testing TLS Parameters
 
+{{< tabpane text=true >}}
+{{% tab header="With External LoadBalancer Support" %}}
+
+Attempt to connect using an unsupported TLS version:
+
+```shell
+curl -v -HHost:www.sample.com --resolve "www.sample.com:443:${GATEWAY_HOST}" \
+--cacert sample.com.crt --tlsv1.2 --tls-max 1.2 https://www.sample.com/get -I
+```
+
+You should receive the following error:
+```
+[...]
+
+* ALPN: curl offers h2,http/1.1
+* (304) (OUT), TLS handshake, Client hello (1):
+* LibreSSL/3.3.6: error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
+* Closing connection
+curl: (35) LibreSSL/3.3.6: error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
+```
+
+The output shows that the connection fails due to an unsupported TLS protocol version used by the client. Now, connect to the Gateway without specifying a client version, and note that the connection is established with TLSv1.3.
+
+```shell
+curl -v -HHost:www.sample.com --resolve "www.sample.com:443:${GATEWAY_HOST}" \
+--cacert sample.com.crt https://www.sample.com/get -I
+```
+
+The command above should succeed and output the following:
+```
+[...]
+* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
+```
+
+
+{{% /tab %}}
+{{% tab header="Without LoadBalancer Support" %}}
+
 Attempt to connecting using an unsupported TLS version:
 
 ```shell
 curl -v -HHost:www.sample.com --resolve "www.sample.com:8443:127.0.0.1" \
 --cacert sample.com.crt --tlsv1.2 --tls-max 1.2 https://www.sample.com:8443/get -I
+```
 
+You should receive the following error:
+```
 [...]
 
 * ALPN: curl offers h2,http/1.1
@@ -585,12 +756,17 @@ to the Gateway without specifying a client version, and note that the connection
 ```shell
 curl -v -HHost:www.sample.com --resolve "www.sample.com:8443:127.0.0.1" \
 --cacert sample.com.crt https://www.sample.com:8443/get -I
+```
 
+The command above should succeed and output the following:
+```
 [...]
-
 * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
 ```
 
+{{% /tab %}}
+{{< /tabpane >}}
+
 ## Next Steps
 
 Checkout the [Developer Guide](../../../contributions/develop) to get involved in the project.