Repository: envoyproxy/gateway
Pull request #5451 (2025-03-10 06:05:01)

feat: Add support for BackendTLSPolicy to target ServiceImport
## Problem

BackendTLSPolicy only supports Service and Backend targetRefs, it doesn't support ServiceImport.

## Fix

This PR adds support for that case.

## Testing Done

Added a new translator test case that verifies `tls` settings appear in the xDS IR for ServiceImports.

## Additional Info

Fixes https://github.com/envoyproxy/gateway/issues/5376

Release Notes: Yes


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/5451?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 `76.92308%` with `18 lines` in your changes missing coverage. Please review.
> Project coverage is 65.33%. Comparing base [(`8b74d01`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/8b74d01a6d496e240b0a96e8d731557e16ce4df2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`83c1ad0`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/83c1ad0f42504a7d6a1dc54a910d2be1aecf4afa?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 8 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/5451?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/route.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5451?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Froute.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9yb3V0ZS5nbw==) | 76.92% | [16 Missing and 2 partials :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5451?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    #5451      +/-   ##
==========================================
+ Coverage   65.30%   65.33%   +0.02%     
==========================================
  Files         213      213              
  Lines       33915    33915              
==========================================
+ Hits        22147    22157      +10     
+ Misses      10438    10432       -6     
+ Partials     1330     1326       -4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/5451?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>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
arkodg: all tests are failing, is this a flake or a real issue ?
nareddyt: > all tests are failing, is this a flake or a real issue ?

Failing test, I will double check. Not sure how my code changes would cause this failure.

```
    --- FAIL: TestE2E/OIDC_with_BackendCluster (100.22s)
        --- FAIL: TestE2E/OIDC_with_BackendCluster/oidc_provider_represented_by_a_BackendCluster (100.11s)
```
arkodg: cc @zhaohuabing 
nareddyt: > > all tests are failing, is this a flake or a real issue ?
> 
> Failing test, I will double check. Not sure how my code changes would cause this failure.
> 
> ```
>     --- FAIL: TestE2E/OIDC_with_BackendCluster (100.22s)
>         --- FAIL: TestE2E/OIDC_with_BackendCluster/oidc_provider_represented_by_a_BackendCluster (100.11s)
> ```

It was a real issue, fixed the switch-case in https://github.com/envoyproxy/gateway/pull/5451/commits/9bf1a877eb74191c481c761f11dcddf09bbf21a1. I forgot golang switch-case syntax is different from C++ 😓 
nareddyt: /retest

Patch:
diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go
index f32cfeb1e..60ece038e 100644
--- a/internal/gatewayapi/route.go
+++ b/internal/gatewayapi/route.go
@@ -1313,92 +1313,43 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
 		envoyProxy = gatewayCtx.envoyProxy
 	}
 
-	var (
-		endpoints []*ir.DestinationEndpoint
-		addrType  *ir.DestinationAddressType
-	)
 	protocol := inspectAppProtocolByRouteKind(routeType)
 
 	switch KindDerefOr(backendRef.Kind, resource.KindService) {
 	case resource.KindServiceImport:
-		serviceImport := resources.GetServiceImport(backendNamespace, string(backendRef.Name))
-		var servicePort mcsapiv1a1.ServicePort
-		for _, port := range serviceImport.Spec.Ports {
-			if port.Port == int32(*backendRef.Port) {
-				servicePort = port
-				break
-			}
-		}
-
-		if !t.IsEnvoyServiceRouting(envoyProxy) {
-			endpointSlices := resources.GetEndpointSlicesForBackend(backendNamespace, string(backendRef.Name), KindDerefOr(backendRef.Kind, resource.KindService))
-			endpoints, addrType = getIREndpointsFromEndpointSlices(endpointSlices, servicePort.Name, servicePort.Protocol)
-		} else {
-			backendIps := resources.GetServiceImport(backendNamespace, string(backendRef.Name)).Spec.IPs
-			for _, ip := range backendIps {
-				ep := ir.NewDestEndpoint(ip, uint32(*backendRef.Port), false, nil)
-				endpoints = append(endpoints, ep)
-			}
-		}
-
-		ds = &ir.DestinationSetting{
-			Name:        name,
-			Weight:      &weight,
-			Protocol:    protocol,
-			Endpoints:   endpoints,
-			AddressType: addrType,
-		}
+		ds = t.processServiceImportDestinationSetting(name, backendRef.BackendObjectReference, backendNamespace, protocol, resources, envoyProxy)
 
 	case resource.KindService:
 		ds = t.processServiceDestinationSetting(name, backendRef.BackendObjectReference, backendNamespace, protocol, resources, envoyProxy)
-		ds.TLS, err = t.applyBackendTLSSetting(
-			backendRef.BackendObjectReference,
-			backendNamespace,
-			gwapiv1a2.ParentReference{
-				Group:       parentRef.Group,
-				Kind:        parentRef.Kind,
-				Namespace:   parentRef.Namespace,
-				Name:        parentRef.Name,
-				SectionName: parentRef.SectionName,
-				Port:        parentRef.Port,
-			},
-			resources,
-			envoyProxy,
-		)
-		if err != nil {
-			return nil, err
-		}
-		ds.Filters, err = t.processDestinationFilters(routeType, backendRefContext, parentRef, route, resources)
-		if err != nil {
-			return nil, err
-		}
 		svc := resources.GetService(backendNamespace, string(backendRef.Name))
 		ds.IPFamily = getServiceIPFamily(svc)
 		ds.ZoneAwareRoutingEnabled = isZoneAwareRoutingEnabled(svc)
 
 	case egv1a1.KindBackend:
 		ds = t.processBackendDestinationSetting(name, backendRef.BackendObjectReference, backendNamespace, protocol, resources)
-		ds.TLS, err = t.applyBackendTLSSetting(
-			backendRef.BackendObjectReference,
-			backendNamespace,
-			gwapiv1a2.ParentReference{
-				Group:       parentRef.Group,
-				Kind:        parentRef.Kind,
-				Namespace:   parentRef.Namespace,
-				Name:        parentRef.Name,
-				SectionName: parentRef.SectionName,
-				Port:        parentRef.Port,
-			},
-			resources,
-			envoyProxy,
-		)
-		if err != nil {
-			return nil, err
-		}
-		ds.Filters, err = t.processDestinationFilters(routeType, backendRefContext, parentRef, route, resources)
-		if err != nil {
-			return nil, err
-		}
+	}
+
+	ds.TLS, err = t.applyBackendTLSSetting(
+		backendRef.BackendObjectReference,
+		backendNamespace,
+		gwapiv1a2.ParentReference{
+			Group:       parentRef.Group,
+			Kind:        parentRef.Kind,
+			Namespace:   parentRef.Namespace,
+			Name:        parentRef.Name,
+			SectionName: parentRef.SectionName,
+			Port:        parentRef.Port,
+		},
+		resources,
+		envoyProxy,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	ds.Filters, err = t.processDestinationFilters(routeType, backendRefContext, parentRef, route, resources)
+	if err != nil {
+		return nil, err
 	}
 
 	if err := validateDestinationSettings(ds, t.IsEnvoyServiceRouting(envoyProxy), backendRef.Kind); err != nil {
@@ -1433,6 +1384,55 @@ func validateDestinationSettings(destinationSettings *ir.DestinationSetting, end
 	return nil
 }
 
+func (t *Translator) processServiceImportDestinationSetting(
+	name string,
+	backendRef gwapiv1.BackendObjectReference,
+	backendNamespace string,
+	protocol ir.AppProtocol,
+	resources *resource.Resources,
+	envoyProxy *egv1a1.EnvoyProxy,
+) *ir.DestinationSetting {
+	var (
+		endpoints []*ir.DestinationEndpoint
+		addrType  *ir.DestinationAddressType
+	)
+
+	serviceImport := resources.GetServiceImport(backendNamespace, string(backendRef.Name))
+	var servicePort mcsapiv1a1.ServicePort
+	for _, port := range serviceImport.Spec.Ports {
+		if port.Port == int32(*backendRef.Port) {
+			servicePort = port
+			break
+		}
+	}
+
+	// TODO(#5485): Should these protocols be supported for ServiceImport?
+	//
+	// if servicePort.AppProtocol != nil {
+	// 	protocol = serviceAppProtocolToIRAppProtocol(*servicePort.AppProtocol)
+	// }
+
+	// Route to endpoints by default
+	if !t.IsEnvoyServiceRouting(envoyProxy) {
+		endpointSlices := resources.GetEndpointSlicesForBackend(backendNamespace, string(backendRef.Name), resource.KindServiceImport)
+		endpoints, addrType = getIREndpointsFromEndpointSlices(endpointSlices, servicePort.Name, servicePort.Protocol)
+	} else {
+		// Fall back to Service ClusterIP routing
+		backendIps := resources.GetServiceImport(backendNamespace, string(backendRef.Name)).Spec.IPs
+		for _, ip := range backendIps {
+			ep := ir.NewDestEndpoint(ip, uint32(*backendRef.Port), false, nil)
+			endpoints = append(endpoints, ep)
+		}
+	}
+
+	return &ir.DestinationSetting{
+		Name:        name,
+		Protocol:    protocol,
+		Endpoints:   endpoints,
+		AddressType: addrType,
+	}
+}
+
 func (t *Translator) processServiceDestinationSetting(
 	name string,
 	backendRef gwapiv1.BackendObjectReference,
@@ -1457,12 +1457,7 @@ func (t *Translator) processServiceDestinationSetting(
 
 	// support HTTPRouteBackendProtocolH2C/GRPC
 	if servicePort.AppProtocol != nil {
-		switch *servicePort.AppProtocol {
-		case "kubernetes.io/h2c":
-			protocol = ir.HTTP2
-		case "grpc":
-			protocol = ir.GRPC
-		}
+		protocol = serviceAppProtocolToIRAppProtocol(*servicePort.AppProtocol, protocol)
 	}
 
 	// Route to endpoints by default
@@ -1750,8 +1745,8 @@ func getTargetBackendReference(backendRef gwapiv1a2.BackendObjectReference, back
 		return ref
 	}
 
-	// Set the section name to the port name if the backend is a Kubernetes Service
-	if backendRef.Kind == nil || *backendRef.Kind == resource.KindService {
+	switch {
+	case backendRef.Kind == nil || *backendRef.Kind == resource.KindService:
 		if service := resources.GetService(backendNamespace, string(backendRef.Name)); service != nil {
 			for _, port := range service.Spec.Ports {
 				if port.Port == int32(*backendRef.Port) {
@@ -1762,7 +1757,20 @@ func getTargetBackendReference(backendRef gwapiv1a2.BackendObjectReference, back
 				}
 			}
 		}
-	} else {
+
+	case *backendRef.Kind == resource.KindServiceImport:
+		if si := resources.GetServiceImport(backendNamespace, string(backendRef.Name)); si != nil {
+			for _, port := range si.Spec.Ports {
+				if port.Port == int32(*backendRef.Port) {
+					if port.Name != "" {
+						ref.SectionName = SectionNamePtr(port.Name)
+						break
+					}
+				}
+			}
+		}
+
+	default:
 		// Set the section name to the port number if the backend is a EG Backend
 		ref.SectionName = SectionNamePtr(strconv.Itoa(int(*backendRef.Port)))
 	}
@@ -1819,12 +1827,7 @@ func (t *Translator) processBackendDestinationSetting(name string, backendRef gw
 	}
 
 	for _, ap := range backend.Spec.AppProtocols {
-		switch ap {
-		case egv1a1.AppProtocolTypeH2C:
-			protocol = ir.HTTP2
-		case "grpc":
-			protocol = ir.GRPC
-		}
+		protocol = backendAppProtocolToIRAppProtocol(ap, protocol)
 	}
 
 	ds := &ir.DestinationSetting{
@@ -1843,3 +1846,25 @@ func (t *Translator) processBackendDestinationSetting(name string, backendRef gw
 
 	return ds
 }
+
+func serviceAppProtocolToIRAppProtocol(ap string, defaultProtocol ir.AppProtocol) ir.AppProtocol {
+	switch ap {
+	case "kubernetes.io/h2c":
+		return ir.HTTP2
+	case "grpc":
+		return ir.GRPC
+	default:
+		return defaultProtocol
+	}
+}
+
+func backendAppProtocolToIRAppProtocol(ap egv1a1.AppProtocolType, defaultProtocol ir.AppProtocol) ir.AppProtocol {
+	switch ap {
+	case egv1a1.AppProtocolTypeH2C:
+		return ir.HTTP2
+	case "grpc":
+		return ir.GRPC
+	default:
+		return defaultProtocol
+	}
+}
diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go
index f182f10ed..f5446a7dd 100644
--- a/internal/gatewayapi/runner/runner.go
+++ b/internal/gatewayapi/runner/runner.go
@@ -13,7 +13,6 @@ import (
 	"os"
 	"path"
 	"reflect"
-	"runtime"
 
 	"github.com/docker/docker/pkg/fileutils"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -97,13 +96,11 @@ func (r *Runner) startWasmCache(ctx context.Context) {
 		return
 	}
 	cacheOption := wasm.CacheOptions{}
-	switch runtime.GOOS {
-	case "darwin":
-		// On darwin, /var/lib/eg/wasm is not writable by non-root users.
+	if r.Config.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes {
+		cacheOption.CacheDir = "/var/lib/eg/wasm"
+	} else {
 		h, _ := os.UserHomeDir() // Assume we always get the home directory.
 		cacheOption.CacheDir = path.Join(h, ".eg", "wasm")
-	default:
-		cacheOption.CacheDir = "/var/lib/eg/wasm"
 	}
 	// Create the file directory if it does not exist.
 	if err = fileutils.CreateIfNotExists(cacheOption.CacheDir, true); err != nil {
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index 87ee5adc9..c995a3e06 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -22,6 +22,7 @@ new features: |
   Added support for forwarding the authenticated username to the backend via a configurable header in BasicAuth
   Added support for HTTP Methods and Headers based authorization in SecurityPolicy
   Added support for zone aware routing
+  Added support for BackendTLSPolicy to target ServiceImport
   Added support for per-host circuit breaker thresholds
 
 bug fixes: |
diff --git a/site/content/en/v1.2/troubleshooting/configuration.md b/site/content/en/v1.2/troubleshooting/configuration.md
index 87b774fd8..e79498e9e 100644
--- a/site/content/en/v1.2/troubleshooting/configuration.md
+++ b/site/content/en/v1.2/troubleshooting/configuration.md
@@ -1,5 +1,5 @@
 +++
-title = "Configuruation Issues"
+title = "Configuration Issues"
 +++
 
 ## Overview
diff --git a/site/content/en/v1.3/troubleshooting/configuration.md b/site/content/en/v1.3/troubleshooting/configuration.md
index 87b774fd8..e79498e9e 100644
--- a/site/content/en/v1.3/troubleshooting/configuration.md
+++ b/site/content/en/v1.3/troubleshooting/configuration.md
@@ -1,5 +1,5 @@
 +++
-title = "Configuruation Issues"
+title = "Configuration Issues"
 +++
 
 ## Overview


Test patch:
diff --git a/internal/gatewayapi/testdata/backendtlspolicy-serviceimport-target.in.yaml b/internal/gatewayapi/testdata/backendtlspolicy-serviceimport-target.in.yaml
new file mode 100644
index 000000000..a93dccc99
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtlspolicy-serviceimport-target.in.yaml
@@ -0,0 +1,100 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      name: gateway-btls
+      namespace: envoy-gateway
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      name: httproute-btls
+      namespace: envoy-gateway
+    spec:
+      parentRefs:
+        - namespace: envoy-gateway
+          name: gateway-btls
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                type: Exact
+                value: "/exact"
+          backendRefs:
+            - group: multicluster.x-k8s.io
+              kind: ServiceImport
+              name: service-import-1
+              namespace: default
+              port: 8080
+
+referenceGrants:
+  - apiVersion: gateway.networking.k8s.io/v1alpha2
+    kind: ReferenceGrant
+    metadata:
+      name: refg-route-svc
+      namespace: default
+    spec:
+      from:
+        - group: gateway.networking.k8s.io
+          kind: HTTPRoute
+          namespace: envoy-gateway
+      to:
+        - group: multicluster.x-k8s.io
+          kind: ServiceImport
+
+serviceImports:
+  - apiVersion: multicluster.x-k8s.io/v1alpha1
+    kind: ServiceImport
+    metadata:
+      name: service-import-1
+      namespace: default
+    spec:
+      ips:
+        - 7.7.7.7
+      ports:
+        - port: 8080
+          name: http
+          protocol: TCP
+
+endpointSlices:
+  - apiVersion: discovery.k8s.io/v1
+    kind: EndpointSlice
+    metadata:
+      name: service-import-1
+      namespace: default
+      labels:
+        kubernetes.io/service-name: service-import-1
+    addressType: IPv4
+    ports:
+      - name: http
+        protocol: TCP
+        port: 8080
+    endpoints:
+      - addresses:
+          - "10.244.0.11"
+        conditions:
+          ready: true
+backendTLSPolicies:
+  - apiVersion: gateway.networking.k8s.io/v1alpha2
+    kind: BackendTLSPolicy
+    metadata:
+      name: policy-btls
+      namespace: default
+    spec:
+      targetRefs:
+        - group: multicluster.x-k8s.io
+          kind: ServiceImport
+          name: service-import-1
+          sectionName: http
+      validation:
+        wellKnownCACertificates: System
+        hostname: example.com
diff --git a/internal/gatewayapi/testdata/backendtlspolicy-serviceimport-target.out.yaml b/internal/gatewayapi/testdata/backendtlspolicy-serviceimport-target.out.yaml
new file mode 100644
index 000000000..9e3a09116
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtlspolicy-serviceimport-target.out.yaml
@@ -0,0 +1,178 @@
+backendTLSPolicies:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: BackendTLSPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-btls
+    namespace: default
+  spec:
+    targetRefs:
+    - group: multicluster.x-k8s.io
+      kind: ServiceImport
+      name: service-import-1
+      sectionName: http
+    validation:
+      hostname: example.com
+      wellKnownCACertificates: System
+  status:
+    ancestors:
+    - ancestorRef:
+        name: gateway-btls
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-btls
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-btls
+    namespace: envoy-gateway
+  spec:
+    parentRefs:
+    - name: gateway-btls
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - group: multicluster.x-k8s.io
+        kind: ServiceImport
+        name: service-import-1
+        namespace: default
+        port: 8080
+      matches:
+      - path:
+          type: Exact
+          value: /exact
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-btls
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-btls:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-btls/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-btls
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-btls
+xdsIR:
+  envoy-gateway/gateway-btls:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-btls
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-btls/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/envoy-gateway/httproute-btls/rule/0
+          settings:
+          - name: httproute/envoy-gateway/httproute-btls/rule/0/backend/0
+            protocol: HTTP
+            tls:
+              alpnProtocols: null
+              caCertificate:
+                name: policy-btls/default-ca
+              sni: example.com
+              useSystemTrustStore: true
+            weight: 1
+        directResponse:
+          statusCode: 500
+        hostname: '*'
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-btls
+          namespace: envoy-gateway
+        name: httproute/envoy-gateway/httproute-btls/rule/0/match/0/*
+        pathMatch:
+          distinct: false
+          exact: /exact
+          name: ""
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/site/content/en/latest/troubleshooting/configuration.md b/site/content/en/latest/troubleshooting/configuration.md
index 87b774fd8..e79498e9e 100644
--- a/site/content/en/latest/troubleshooting/configuration.md
+++ b/site/content/en/latest/troubleshooting/configuration.md
@@ -1,5 +1,5 @@
 +++
-title = "Configuruation Issues"
+title = "Configuration Issues"
 +++
 
 ## Overview
diff --git a/test/fuzz/oss_fuzz_build.sh b/test/fuzz/oss_fuzz_build.sh
new file mode 100755
index 000000000..5aacf4f9a
--- /dev/null
+++ b/test/fuzz/oss_fuzz_build.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# 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.
+
+# This file contains code derived from Istio, https://github.com/istio/istio
+# from the source file https://github.com/istio/istio/blob/master/tests/fuzz/oss_fuzz_build.sh
+# and is provided here subject to the following: Copyright Istio Authors SPDX-License-Identifier: Apache-2.0
+
+# Required by `compile_native_go_fuzzer`
+# Ref: https://google.github.io/oss-fuzz/getting-started/new-project-guide/go-lang/#buildsh
+cd "$SRC"
+git clone --depth=1 https://github.com/AdamKorcz/go-118-fuzz-build --branch=include-all-test-files
+cd go-118-fuzz-build
+go build .
+mv go-118-fuzz-build /root/go/bin/
+
+cd "$SRC"/gateway
+
+set -o nounset
+set -o pipefail
+set -o errexit
+set -x
+
+# Create empty file that imports "github.com/AdamKorcz/go-118-fuzz-build/testing"
+# This is a small hack to install this dependency, since it is not used anywhere,
+# and Go would therefore remove it from go.mod once we run "go mod tidy && go mod vendor".
+printf "package envoygateway\nimport _ \"github.com/AdamKorcz/go-118-fuzz-build/testing\"\n" > register.go
+go mod edit -replace github.com/AdamKorcz/go-118-fuzz-build="$SRC"/go-118-fuzz-build
+go mod tidy
+
+
+# compile native-format fuzzers
+compile_native_go_fuzzer github.com/envoyproxy/gateway/test/fuzz FuzzGatewayAPIToXDS FuzzGatewayAPIToXDS
+