Repository: envoyproxy/gateway
Pull request #3821 (2024-07-11 13:38:55)

docs: update v1.1.0-rc.1 release notes
**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"
-->

<!--
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**:
- Adds breaking changes
- Adds Deprecation notice

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Replaces #3810


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/3821?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 69.02%. Comparing base [(`0af2f9f`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/0af2f9f50df631f140db016a5846603548911a32?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`96d17b4`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/96d17b4a39e6fdc6ca90387feb120741d8e75d58?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 1 commits behind head on main.

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


```diff
@@            Coverage Diff             @@
##             main    #3821      +/-   ##
==========================================
- Coverage   69.07%   69.02%   -0.06%     
==========================================
  Files         176      176              
  Lines       21760    21760              
==========================================
- Hits        15030    15019      -11     
- Misses       5652     5661       +9     
- Partials     1078     1080       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/3821?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).


Patch:
diff --git a/examples/extension-server/.gitignore b/examples/extension-server/.gitignore
new file mode 100644
index 000000000..ba077a403
--- /dev/null
+++ b/examples/extension-server/.gitignore
@@ -0,0 +1,1 @@
+bin
diff --git a/examples/extension-server/Makefile b/examples/extension-server/Makefile
new file mode 100644
index 000000000..9131d1b70
--- /dev/null
+++ b/examples/extension-server/Makefile
@@ -0,0 +1,30 @@
+tools.dir = tools
+tools.bindir = tools/bin
+tools.srcdir = tools/src
+
+tools/controller-gen     = $(tools.bindir)/controller-gen
+$(tools.bindir)/%: $(tools.srcdir)/%/pin.go $(tools.srcdir)/%/go.mod
+	cd $(<D) && GOOS= GOARCH= go build -o $(abspath $@) $$(sed -En 's,^import _ "(.*)".*,\1,p' pin.go)
+
+build: generate manifests
+	mkdir -p bin
+	CGO_ENABLED=0 go build -o bin/extension-server ./cmd/extension-server
+
+image: build
+	docker build -t extension-server:latest -f tools/docker/extension-server/Dockerfile .
+
+manifests: $(tools/controller-gen)
+	$(tools/controller-gen) crd:allowDangerousTypes=true paths="./..." output:crd:artifacts:config=charts/extension-server/crds/generated
+
+generate: $(tools/controller-gen)
+	$(tools/controller-gen) object:headerFile="$(tools.dir)/boilerplate.generatego.txt",year=2024 paths="{./api/...}"
+
+tools.clean: # Remove all tools
+	@$(LOG_TARGET)
+	rm -rf $(tools.bindir)
+
+clean: tools.clean
+	rm -fr bin
+
+
+.PHONY: build image manifests generate tools.clean clean
diff --git a/examples/extension-server/api/doc.go b/examples/extension-server/api/doc.go
new file mode 100644
index 000000000..912e4dd16
--- /dev/null
+++ b/examples/extension-server/api/doc.go
@@ -0,0 +1,6 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package api
diff --git a/examples/extension-server/api/v1alpha1/doc.go b/examples/extension-server/api/v1alpha1/doc.go
new file mode 100644
index 000000000..519e5dde5
--- /dev/null
+++ b/examples/extension-server/api/v1alpha1/doc.go
@@ -0,0 +1,8 @@
+// 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.
+
+// +kubebuilder:object:generate=true
+// +groupName=example.extensions.io
+package v1alpha1
diff --git a/examples/extension-server/api/v1alpha1/groupversion_info.go b/examples/extension-server/api/v1alpha1/groupversion_info.go
new file mode 100644
index 000000000..bdda3642d
--- /dev/null
+++ b/examples/extension-server/api/v1alpha1/groupversion_info.go
@@ -0,0 +1,25 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package v1alpha1
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+const GroupName = "example.extensions.io"
+
+var (
+
+	// GroupVersion is group version used to register these objects
+	GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
+	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+	// AddToScheme adds the types in this group-version to the given scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+)
diff --git a/examples/extension-server/api/v1alpha1/listenercontext_types.go b/examples/extension-server/api/v1alpha1/listenercontext_types.go
new file mode 100644
index 000000000..6e6884a9c
--- /dev/null
+++ b/examples/extension-server/api/v1alpha1/listenercontext_types.go
@@ -0,0 +1,42 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+)
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+//
+// ListenerContext provides an example extension policy context resource.
+type ListenerContextExample struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec ListenerContextExampleSpec `json:"spec"`
+}
+
+type ListenerContextExampleSpec struct {
+	TargetRefs []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName `json:"targetRefs"`
+
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
+
+// +kubebuilder:object:root=true
+//
+// ListenerContextList contains a list of ListenerContext resources.
+type ListenerContextExampleList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []ListenerContextExample `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&ListenerContextExample{}, &ListenerContextExampleList{})
+}
diff --git a/examples/extension-server/api/v1alpha1/zz_generated.deepcopy.go b/examples/extension-server/api/v1alpha1/zz_generated.deepcopy.go
new file mode 100644
index 000000000..3090bad5e
--- /dev/null
+++ b/examples/extension-server/api/v1alpha1/zz_generated.deepcopy.go
@@ -0,0 +1,95 @@
+//go:build !ignore_autogenerated
+
+// 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.
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/gateway-api/apis/v1alpha2"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerContextExample) DeepCopyInto(out *ListenerContextExample) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerContextExample.
+func (in *ListenerContextExample) DeepCopy() *ListenerContextExample {
+	if in == nil {
+		return nil
+	}
+	out := new(ListenerContextExample)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ListenerContextExample) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerContextExampleList) DeepCopyInto(out *ListenerContextExampleList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ListenerContextExample, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerContextExampleList.
+func (in *ListenerContextExampleList) DeepCopy() *ListenerContextExampleList {
+	if in == nil {
+		return nil
+	}
+	out := new(ListenerContextExampleList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ListenerContextExampleList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerContextExampleSpec) DeepCopyInto(out *ListenerContextExampleSpec) {
+	*out = *in
+	if in.TargetRefs != nil {
+		in, out := &in.TargetRefs, &out.TargetRefs
+		*out = make([]v1alpha2.LocalPolicyTargetReferenceWithSectionName, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerContextExampleSpec.
+func (in *ListenerContextExampleSpec) DeepCopy() *ListenerContextExampleSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ListenerContextExampleSpec)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/examples/extension-server/charts/extension-server/.helmignore b/examples/extension-server/charts/extension-server/.helmignore
new file mode 100644
index 000000000..0e8a0eb36
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/examples/extension-server/charts/extension-server/Chart.yaml b/examples/extension-server/charts/extension-server/Chart.yaml
new file mode 100644
index 000000000..b0ebf3b8a
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: extension-server
+description: A minimal chart to install the example extension-server for Envoy Gateway
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "0.0.1"
diff --git a/examples/extension-server/charts/extension-server/crds/generated/example.extensions.io_listenercontextexamples.yaml b/examples/extension-server/charts/extension-server/crds/generated/example.extensions.io_listenercontextexamples.yaml
new file mode 100644
index 000000000..913ddac45
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/crds/generated/example.extensions.io_listenercontextexamples.yaml
@@ -0,0 +1,112 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.15.0
+  name: listenercontextexamples.example.extensions.io
+spec:
+  group: example.extensions.io
+  names:
+    kind: ListenerContextExample
+    listKind: ListenerContextExampleList
+    plural: listenercontextexamples
+    singular: listenercontextexample
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: ListenerContext provides an example extension policy context
+          resource.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            properties:
+              password:
+                type: string
+              targetRefs:
+                items:
+                  description: |-
+                    LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a
+                    direct policy to. This should be used as part of Policy resources that can
+                    target single resources. For more information on how this policy attachment
+                    mode works, and a sample Policy resource, refer to the policy attachment
+                    documentation for Gateway API.
+
+
+                    Note: This should only be used for direct policy attachment when references
+                    to SectionName are actually needed. In all other cases,
+                    LocalPolicyTargetReference should be used.
+                  properties:
+                    group:
+                      description: Group is the group of the target resource.
+                      maxLength: 253
+                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                      type: string
+                    kind:
+                      description: Kind is kind of the target resource.
+                      maxLength: 63
+                      minLength: 1
+                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
+                      type: string
+                    name:
+                      description: Name is the name of the target resource.
+                      maxLength: 253
+                      minLength: 1
+                      type: string
+                    sectionName:
+                      description: |-
+                        SectionName is the name of a section within the target resource. When
+                        unspecified, this targetRef targets the entire resource. In the following
+                        resources, SectionName is interpreted as the following:
+
+
+                        * Gateway: Listener name
+                        * HTTPRoute: HTTPRouteRule name
+                        * Service: Port name
+
+
+                        If a SectionName is specified, but does not exist on the targeted object,
+                        the Policy must fail to attach, and the policy implementation should record
+                        a `ResolvedRefs` or similar Condition in the Policy's status.
+                      maxLength: 253
+                      minLength: 1
+                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                      type: string
+                  required:
+                  - group
+                  - kind
+                  - name
+                  type: object
+                type: array
+              username:
+                type: string
+            required:
+            - password
+            - targetRefs
+            - username
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/examples/extension-server/charts/extension-server/templates/NOTES.txt b/examples/extension-server/charts/extension-server/templates/NOTES.txt
new file mode 100644
index 000000000..39de879f7
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/templates/NOTES.txt
@@ -0,0 +1,1 @@
+The example extension server is now deployed.
diff --git a/examples/extension-server/charts/extension-server/templates/_helpers.tpl b/examples/extension-server/charts/extension-server/templates/_helpers.tpl
new file mode 100644
index 000000000..3fdf84039
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "extension-server.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "extension-server.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "extension-server.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "extension-server.labels" -}}
+helm.sh/chart: {{ include "extension-server.chart" . }}
+{{ include "extension-server.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "extension-server.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "extension-server.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "extension-server.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "extension-server.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/examples/extension-server/charts/extension-server/templates/deployment.yaml b/examples/extension-server/charts/extension-server/templates/deployment.yaml
new file mode 100644
index 000000000..673d457ce
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/templates/deployment.yaml
@@ -0,0 +1,51 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "extension-server.fullname" . }}
+  labels:
+    {{- include "extension-server.labels" . | nindent 4 }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  selector:
+    matchLabels:
+      {{- include "extension-server.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "extension-server.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "extension-server.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          ports:
+            - name: extserver
+              containerPort: 5005
+              protocol: TCP
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/examples/extension-server/charts/extension-server/templates/service.yaml b/examples/extension-server/charts/extension-server/templates/service.yaml
new file mode 100644
index 000000000..af50a943f
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "extension-server.fullname" . }}
+  labels:
+    {{- include "extension-server.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: 5005
+      protocol: TCP
+      name: extserver
+  selector:
+    {{- include "extension-server.selectorLabels" . | nindent 4 }}
diff --git a/examples/extension-server/charts/extension-server/templates/serviceaccount.yaml b/examples/extension-server/charts/extension-server/templates/serviceaccount.yaml
new file mode 100644
index 000000000..0cdb0eee9
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "extension-server.serviceAccountName" . }}
+  labels:
+    {{- include "extension-server.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/examples/extension-server/charts/extension-server/values.yaml b/examples/extension-server/charts/extension-server/values.yaml
new file mode 100644
index 000000000..023dc0a98
--- /dev/null
+++ b/examples/extension-server/charts/extension-server/values.yaml
@@ -0,0 +1,59 @@
+# Default values for extension-server.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: extension-server
+  pullPolicy: IfNotPresent
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: "latest"
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: true
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: ""
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: ClusterIP
+  port: 5005
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
diff --git a/examples/extension-server/cmd/extension-server/main.go b/examples/extension-server/cmd/extension-server/main.go
new file mode 100644
index 000000000..33e08ddc9
--- /dev/null
+++ b/examples/extension-server/cmd/extension-server/main.go
@@ -0,0 +1,96 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package main
+
+import (
+	"fmt"
+	"log/slog"
+	"net"
+	"os"
+	"os/signal"
+	"syscall"
+
+	pb "github.com/envoyproxy/gateway/proto/extension"
+	"github.com/urfave/cli/v2"
+	"google.golang.org/grpc"
+
+	"github.com/exampleorg/envoygateway-extension/internal/extensionserver"
+)
+
+func main() {
+	app := cli.App{
+		Name:           "extension-server",
+		Version:        "0.0.1",
+		Description:    "Example Envoy Gateway Extension Server",
+		DefaultCommand: "server",
+		Commands: []*cli.Command{
+			{
+				Name:   "server",
+				Usage:  "runs the Extension Server",
+				Before: handleSignals,
+				Action: startExtensionServer,
+				Flags: []cli.Flag{
+					&cli.StringFlag{
+						Name:        "host",
+						Usage:       "the host on which to listen",
+						DefaultText: "0.0.0.0",
+						Value:       "0.0.0.0",
+					},
+					&cli.IntFlag{
+						Name:        "port",
+						Usage:       "the port on which to listen",
+						DefaultText: "5005",
+						Value:       5005,
+					},
+					&cli.StringFlag{
+						Name:        "log-level",
+						Usage:       "the log level, should be one of Debug/Info/Warn/Error",
+						DefaultText: "Info",
+						Value:       "Info",
+					},
+				},
+			},
+		},
+	}
+	app.Run(os.Args)
+
+}
+
+var grpcServer *grpc.Server
+
+func handleSignals(cCtx *cli.Context) error {
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, os.Interrupt, syscall.SIGQUIT)
+	go func() {
+		for _ = range c {
+			if grpcServer != nil {
+				grpcServer.Stop()
+				os.Exit(0)
+			}
+		}
+	}()
+	return nil
+}
+
+func startExtensionServer(cCtx *cli.Context) error {
+	var level slog.Level
+	if err := level.UnmarshalText([]byte(cCtx.String("log-level"))); err != nil {
+		level = slog.LevelInfo
+	}
+	logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
+		Level: level,
+	}))
+	address := fmt.Sprintf("%s:%d", cCtx.String("host"), cCtx.Int("port"))
+	logger.Info("Starting the extension server", slog.String("host", address))
+	lis, err := net.Listen("tcp", address)
+	if err != nil {
+		return err
+	}
+	var opts []grpc.ServerOption
+	grpcServer = grpc.NewServer(opts...)
+	pb.RegisterEnvoyGatewayExtensionServer(grpcServer, extensionserver.New(logger))
+	return grpcServer.Serve(lis)
+}
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
new file mode 100644
index 000000000..667d60424
--- /dev/null
+++ b/examples/extension-server/go.mod
@@ -0,0 +1,46 @@
+module github.com/exampleorg/envoygateway-extension
+
+go 1.22.5
+
+require (
+	github.com/envoyproxy/gateway v1.0.2
+	github.com/envoyproxy/go-control-plane v0.12.1-0.20240612043845-c54ec4ce422d
+	github.com/urfave/cli/v2 v2.27.2
+	google.golang.org/grpc v1.65.0
+	k8s.io/apimachinery v0.30.2
+	sigs.k8s.io/controller-runtime v0.18.4
+	sigs.k8s.io/gateway-api v1.1.0
+)
+
+require (
+	cel.dev/expr v0.15.0 // indirect
+	github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
+	github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
+	github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
+	github.com/go-logr/logr v1.4.2 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/kr/text v0.2.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
+	golang.org/x/net v0.26.0 // indirect
+	golang.org/x/sys v0.21.0 // indirect
+	golang.org/x/text v0.16.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	k8s.io/klog/v2 v2.120.1 // indirect
+	k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect
+	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
+	sigs.k8s.io/yaml v1.4.0 // indirect
+)
+
+replace github.com/envoyproxy/gateway => ../../
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
new file mode 100644
index 000000000..3b9eb9675
--- /dev/null
+++ b/examples/extension-server/go.sum
@@ -0,0 +1,138 @@
+cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
+cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
+github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
+github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
+github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
+github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.12.1-0.20240612043845-c54ec4ce422d h1:RopQsG28t61pLLZRkwzwBsi60yDsOP8RvW47A3eAcGo=
+github.com/envoyproxy/go-control-plane v0.12.1-0.20240612043845-c54ec4ce422d/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
+github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
+github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
+github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
+github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
+github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
+github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
+github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
+github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
+google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
+google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
+google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI=
+k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI=
+k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
+k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
+k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
+k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw=
+sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg=
+sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
+sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/examples/extension-server/internal/extensionserver/htpasswd.go b/examples/extension-server/internal/extensionserver/htpasswd.go
new file mode 100644
index 000000000..0e43c7553
--- /dev/null
+++ b/examples/extension-server/internal/extensionserver/htpasswd.go
@@ -0,0 +1,38 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package extensionserver
+
+import (
+	"crypto/sha1"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"strings"
+)
+
+type htpasswd struct {
+	Users map[string]string
+}
+
+func NewHtpasswd() htpasswd {
+	return htpasswd{
+		Users: map[string]string{},
+	}
+}
+
+func (h *htpasswd) AddUser(user, password string) {
+	s := sha1.New()
+	io.WriteString(s, password)
+	h.Users[user] = fmt.Sprintf("{SHA}%s", base64.StdEncoding.EncodeToString(s.Sum(nil)))
+}
+
+func (h *htpasswd) String() string {
+	var b strings.Builder
+	for user, password := range h.Users {
+		b.WriteString(fmt.Sprintf("%s:%s\n", user, password))
+	}
+	return b.String()
+}
diff --git a/examples/extension-server/internal/extensionserver/server.go b/examples/extension-server/internal/extensionserver/server.go
new file mode 100644
index 000000000..a2776a9f9
--- /dev/null
+++ b/examples/extension-server/internal/extensionserver/server.go
@@ -0,0 +1,150 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package extensionserver
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"log/slog"
+
+	pb "github.com/envoyproxy/gateway/proto/extension"
+	corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	bav3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/basic_auth/v3"
+	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+	"github.com/envoyproxy/go-control-plane/pkg/wellknown"
+	"google.golang.org/protobuf/types/known/anypb"
+
+	"github.com/exampleorg/envoygateway-extension/api/v1alpha1"
+)
+
+type Server struct {
+	pb.UnimplementedEnvoyGatewayExtensionServer
+
+	log *slog.Logger
+}
+
+func New(logger *slog.Logger) *Server {
+	return &Server{
+		log: logger,
+	}
+}
+
+// PostHTTPListenerModify is called after Envoy Gateway is done generating a
+// Listener xDS configuration and before that configuration is passed on to
+// Envoy Proxy.
+// This example adds Basic Authentication on the Listener level as an example.
+// Note: This implementation is not secure, and should not be used to protect
+// anything important.
+func (s *Server) PostHTTPListenerModify(ctx context.Context, req *pb.PostHTTPListenerModifyRequest) (*pb.PostHTTPListenerModifyResponse, error) {
+	s.log.Info("postHTTPListenerModify callback was invoked")
+	// Collect all of the required username/password combinations from the
+	// provided contexts that were attached to the gateway.
+	passwords := NewHtpasswd()
+	for _, ext := range req.PostListenerContext.ExtensionResources {
+		var listenerContext v1alpha1.ListenerContextExample
+		if err := json.Unmarshal(ext.GetUnstructuredBytes(), &listenerContext); err != nil {
+			s.log.Error("failed to unmarshal the extension", slog.String("error", err.Error()))
+			continue
+		}
+		s.log.Info("processing an extension context", slog.String("username", listenerContext.Spec.Username))
+		passwords.AddUser(listenerContext.Spec.Username, listenerContext.Spec.Password)
+	}
+
+	// First, get the filter chains from the listener
+	filterChains := req.Listener.GetFilterChains()
+	defaultFC := req.Listener.DefaultFilterChain
+	if defaultFC != nil {
+		filterChains = append(filterChains, defaultFC)
+	}
+	// Go over all of the chains, and add the basic authentication http filter
+	for _, currChain := range filterChains {
+		httpConManager, hcmIndex, err := findHCM(currChain)
+		if err != nil {
+			s.log.Error("failed to find an HCM in the current chain", slog.Any("error", err))
+			continue
+		}
+		// If a basic authentication filter already exists, update it. Otherwise, create it.
+		basicAuth, baIndex, err := findBasicAuthFilter(httpConManager.HttpFilters)
+		if err != nil {
+			s.log.Error("failed to unmarshal the existing basicAuth filter", slog.Any("error", err))
+			continue
+		}
+		if baIndex == -1 {
+			// Create a new basic auth filter
+			basicAuth = &bav3.BasicAuth{
+				Users: &corev3.DataSource{
+					Specifier: &corev3.DataSource_InlineString{
+						InlineString: passwords.String(),
+					},
+				},
+				ForwardUsernameHeader: "X-Example-Ext",
+			}
+		} else {
+			// Update the basic auth filter
+			basicAuth.Users.Specifier = &corev3.DataSource_InlineString{
+				InlineString: passwords.String(),
+			}
+		}
+		// Add or update the Basic Authentication filter in the HCM
+		anyBAFilter, _ := anypb.New(basicAuth)
+		if baIndex > -1 {
+			httpConManager.HttpFilters[baIndex].ConfigType = &hcm.HttpFilter_TypedConfig{
+				TypedConfig: anyBAFilter,
+			}
+		} else {
+			filters := []*hcm.HttpFilter{
+				{
+					Name: "envoy.filters.http.basic_auth",
+					ConfigType: &hcm.HttpFilter_TypedConfig{
+						TypedConfig: anyBAFilter,
+					},
+				},
+			}
+			filters = append(filters, httpConManager.HttpFilters...)
+			httpConManager.HttpFilters = filters
+		}
+
+		// Write the updated HCM back to the filter chain
+		anyConnectionMgr, _ := anypb.New(httpConManager)
+		currChain.Filters[hcmIndex].ConfigType = &listenerv3.Filter_TypedConfig{
+			TypedConfig: anyConnectionMgr,
+		}
+	}
+
+	return &pb.PostHTTPListenerModifyResponse{
+		Listener: req.Listener,
+	}, nil
+}
+
+// Tries to find an HTTP connection manager in the provided filter chain.
+func findHCM(filterChain *listenerv3.FilterChain) (*hcm.HttpConnectionManager, int, error) {
+	for filterIndex, filter := range filterChain.Filters {
+		if filter.Name == wellknown.HTTPConnectionManager {
+			hcm := new(hcm.HttpConnectionManager)
+			if err := filter.GetTypedConfig().UnmarshalTo(hcm); err != nil {
+				return nil, -1, err
+			}
+			return hcm, filterIndex, nil
+		}
+	}
+	return nil, -1, fmt.Errorf("unable to find HTTPConnectionManager in FilterChain: %s", filterChain.Name)
+}
+
+// Tries to find the Basic Authentication HTTP filter in the provided chain
+func findBasicAuthFilter(chain []*hcm.HttpFilter) (*bav3.BasicAuth, int, error) {
+	for i, filter := range chain {
+		if filter.Name == "envoy.filters.http.basic_auth" {
+			ba := new(bav3.BasicAuth)
+			if err := filter.GetTypedConfig().UnmarshalTo(ba); err != nil {
+				return nil, -1, err
+			}
+			return ba, i, nil
+		}
+	}
+	return nil, -1, nil
+}
diff --git a/examples/extension-server/tools/boilerplate.generatego.txt b/examples/extension-server/tools/boilerplate.generatego.txt
new file mode 100644
index 000000000..3f9e2deb7
--- /dev/null
+++ b/examples/extension-server/tools/boilerplate.generatego.txt
@@ -0,0 +1,5 @@
+// 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.
+
diff --git a/examples/extension-server/tools/docker/extension-server/Dockerfile b/examples/extension-server/tools/docker/extension-server/Dockerfile
new file mode 100644
index 000000000..4cb549d95
--- /dev/null
+++ b/examples/extension-server/tools/docker/extension-server/Dockerfile
@@ -0,0 +1,6 @@
+FROM gcr.io/distroless/static:nonroot@sha256:e9ac71e2b8e279a8372741b7a0293afda17650d926900233ec3a7b2b7c22a246
+COPY ./bin/extension-server /usr/local/bin/extension-server
+
+USER 65532:65532
+
+ENTRYPOINT ["/usr/local/bin/extension-server"]
diff --git a/examples/extension-server/tools/src/controller-gen/go.mod b/examples/extension-server/tools/src/controller-gen/go.mod
new file mode 100644
index 000000000..3b5da982d
--- /dev/null
+++ b/examples/extension-server/tools/src/controller-gen/go.mod
@@ -0,0 +1,38 @@
+module local
+
+go 1.22.5
+
+require sigs.k8s.io/controller-tools v0.15.0
+
+require (
+	github.com/fatih/color v1.16.0 // indirect
+	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/gobuffalo/flect v1.0.2 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/spf13/cobra v1.8.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	golang.org/x/mod v0.17.0 // indirect
+	golang.org/x/net v0.24.0 // indirect
+	golang.org/x/sync v0.7.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/tools v0.20.0 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	k8s.io/api v0.30.0 // indirect
+	k8s.io/apiextensions-apiserver v0.30.0 // indirect
+	k8s.io/apimachinery v0.30.0 // indirect
+	k8s.io/klog/v2 v2.120.1 // indirect
+	k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 // indirect
+	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
+	sigs.k8s.io/yaml v1.4.0 // indirect
+)
diff --git a/examples/extension-server/tools/src/controller-gen/go.sum b/examples/extension-server/tools/src/controller-gen/go.sum
new file mode 100644
index 000000000..797074f31
--- /dev/null
+++ b/examples/extension-server/tools/src/controller-gen/go.sum
@@ -0,0 +1,136 @@
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA=
+github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
+github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
+golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
+k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
+k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
+k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
+k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
+k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
+k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
+k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 h1:ao5hUqGhsqdm+bYbjH/pRkCs0unBGe9UyDahzs9zQzQ=
+k8s.io/utils v0.0.0-20240423183400-0849a56e8f22/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/controller-tools v0.15.0 h1:4dxdABXGDhIa68Fiwaif0vcu32xfwmgQ+w8p+5CxoAI=
+sigs.k8s.io/controller-tools v0.15.0/go.mod h1:8zUSS2T8Hx0APCNRhJWbS3CAQEbIxLa07khzh7pZmXM=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/examples/extension-server/tools/src/controller-gen/pin.go b/examples/extension-server/tools/src/controller-gen/pin.go
new file mode 100644
index 000000000..82c8d1cf3
--- /dev/null
+++ b/examples/extension-server/tools/src/controller-gen/pin.go
@@ -0,0 +1,11 @@
+// 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.
+
+//go:build pin
+// +build pin
+
+package ignore
+
+import _ "sigs.k8s.io/controller-tools/cmd/controller-gen"
diff --git a/examples/kubernetes/envoy-als.yaml b/examples/kubernetes/envoy-als.yaml
new file mode 100644
index 000000000..41e2d65ab
--- /dev/null
+++ b/examples/kubernetes/envoy-als.yaml
@@ -0,0 +1,231 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: envoy-als
+data:
+  go.mod: |
+    module envoy-als
+    go 1.22
+    require (
+        github.com/envoyproxy/go-control-plane v0.12.0
+        github.com/prometheus/client_golang v1.19.1
+        google.golang.org/grpc v1.64.0
+    )
+
+    require (
+        github.com/beorn7/perks v1.0.1 // indirect
+        github.com/cespare/xxhash/v2 v2.2.0 // indirect
+        github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect
+        github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
+        github.com/golang/protobuf v1.5.4 // indirect
+        github.com/prometheus/client_model v0.5.0 // indirect
+        github.com/prometheus/common v0.48.0 // indirect
+        github.com/prometheus/procfs v0.12.0 // indirect
+        golang.org/x/net v0.22.0 // indirect
+        golang.org/x/sys v0.18.0 // indirect
+        golang.org/x/text v0.14.0 // indirect
+        google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
+        google.golang.org/protobuf v1.33.0 // indirect
+    )
+  go.sum: |
+    github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+    github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+    github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+    github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+    github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc=
+    github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
+    github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+    github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+    github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
+    github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
+    github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
+    github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
+    github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+    github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+    github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+    github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+    github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+    github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+    github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+    github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+    github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
+    github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
+    github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+    github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+    golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+    golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+    golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+    golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+    golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+    golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+    google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
+    google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
+    google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
+    google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+    google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+    google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+  main.go: |
+    package main
+
+    import (
+      "log"
+      "net"
+      "net/http"
+
+      alsv2 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v2"
+      alsv3 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3"
+      "github.com/prometheus/client_golang/prometheus"
+      "github.com/prometheus/client_golang/prometheus/promhttp"
+
+      "google.golang.org/grpc"
+    )
+
+    var (
+      LogCount = prometheus.NewCounterVec(prometheus.CounterOpts{
+          Name: "log_count",
+          Help: "The total number of logs received.",
+      }, []string{"api_version"})
+    )
+
+    func init() {
+      // Register the summary and the histogram with Prometheus's default registry.
+      prometheus.MustRegister(LogCount)
+    }
+
+    type ALSServer struct {
+    }
+
+    func (a *ALSServer) StreamAccessLogs(logStream alsv2.AccessLogService_StreamAccessLogsServer) error {
+      log.Println("Streaming als v2 logs")
+      for {
+          data, err := logStream.Recv()
+          if err != nil {
+              return err
+          }
+
+          httpLogs := data.GetHttpLogs()
+          if httpLogs != nil {
+              LogCount.WithLabelValues("v2").Add(float64(len(httpLogs.LogEntry)))
+          }
+
+          log.Printf("Received v2 log data: %s\n", data.String())
+      }
+    }
+
+    type ALSServerV3 struct {
+    }
+
+    func (a *ALSServerV3) StreamAccessLogs(logStream alsv3.AccessLogService_StreamAccessLogsServer) error {
+      log.Println("Streaming als v3 logs")
+      for {
+          data, err := logStream.Recv()
+          if err != nil {
+              return err
+          }
+
+          httpLogs := data.GetHttpLogs()
+          if httpLogs != nil {
+              LogCount.WithLabelValues("v3").Add(float64(len(httpLogs.LogEntry)))
+          }
+
+          log.Printf("Received v3 log data: %s\n", data.String())
+      }
+    }
+
+    func NewALSServer() *ALSServer {
+      return &ALSServer{}
+    }
+
+    func NewALSServerV3() *ALSServerV3 {
+      return &ALSServerV3{}
+    }
+
+    func main() {
+      mux := http.NewServeMux()
+      if err := addMonitor(mux); err != nil {
+          log.Printf("could not establish self-monitoring: %v\n", err)
+      }
+
+      s := &http.Server{
+          Addr:    ":19001",
+          Handler: mux,
+      }
+
+      go func() {
+          s.ListenAndServe()
+      }()
+
+      listener, err := net.Listen("tcp", "0.0.0.0:8080")
+      if err != nil {
+          log.Fatalf("Failed to start listener on port 8080: %v", err)
+      }
+
+      var opts []grpc.ServerOption
+      grpcServer := grpc.NewServer(opts...)
+      alsv2.RegisterAccessLogServiceServer(grpcServer, NewALSServer())
+      alsv3.RegisterAccessLogServiceServer(grpcServer, NewALSServerV3())
+      log.Println("Starting ALS Server")
+      if err := grpcServer.Serve(listener); err != nil {
+          log.Fatalf("grpc serve err: %v", err)
+      }
+    }
+
+    func addMonitor(mux *http.ServeMux) error {
+      mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}))
+
+      return nil
+    }
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: envoy-als
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: envoy-als
+  template:
+    metadata:
+      annotations:
+        prometheus.io/scrape: "true"
+        prometheus.io/port: "19001"
+      labels:
+        app: envoy-als
+    spec:
+      containers:
+        - name: envoy-als
+          command:
+            - sh
+            - "-c"
+            - "cp -a /app /app-live && cd /app-live && go run . "
+          image: golang:1.22.3-alpine
+          ports:
+            - containerPort: 8080
+            - containerPort: 19001
+          volumeMounts:
+            - name: envoy-als
+              mountPath: /app
+      volumes:
+        - name: envoy-als
+          configMap:
+            name: envoy-als
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: envoy-als
+spec:
+  selector:
+    app: envoy-als
+  type: LoadBalancer
+  ports:
+    - name: grpc-als
+      protocol: TCP
+      appProtocol: grpc
+      port: 8080
+      targetPort: 8080
+    - name: http-monitoring
+      protocol: TCP
+      port: 19001
+      targetPort: 19001
diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go
index fc917d06a..0f7c4f752 100644
--- a/internal/gatewayapi/extensionserverpolicy.go
+++ b/internal/gatewayapi/extensionserverpolicy.go
@@ -18,6 +18,7 @@ import (
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 	gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/status"
 	"github.com/envoyproxy/gateway/internal/ir"
 	"github.com/envoyproxy/gateway/internal/utils"
@@ -50,7 +51,7 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst
 		policy := policy.DeepCopy()
 		var policyStatus gwapiv1a2.PolicyStatus
 		accepted := false
-		targetRefs, err := extractTargetRefs(policy)
+		targetRefs, err := extractTargetRefs(policy, gateways)
 		if err != nil {
 			errs = errors.Join(errs, fmt.Errorf("error finding targetRefs for policy %s: %w", policy.GetName(), err))
 			continue
@@ -96,55 +97,26 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst
 	return res, errs
 }
 
-func extractTargetRefs(policy *unstructured.Unstructured) ([]gwapiv1a2.LocalPolicyTargetReferenceWithSectionName, error) {
-	ret := []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{}
+func extractTargetRefs(policy *unstructured.Unstructured, gateways []*GatewayContext) ([]gwapiv1a2.LocalPolicyTargetReferenceWithSectionName, error) {
 	spec, found := policy.Object["spec"].(map[string]any)
 	if !found {
 		return nil, fmt.Errorf("no targets found for the policy")
 	}
-	targetRefs, found := spec["targetRefs"]
-	if found {
-		if refArr, ok := targetRefs.([]any); ok {
-			for i := range refArr {
-				ref, err := extractSingleTargetRef(refArr[i])
-				if err != nil {
-					return nil, err
-				}
-				ret = append(ret, ref)
-			}
-		} else {
-			return nil, fmt.Errorf("targetRefs is not an array")
-		}
+	specAsJSON, err := json.Marshal(spec)
+	if err != nil {
+		return nil, fmt.Errorf("no targets found for the policy")
 	}
-	targetRef, found := spec["targetRef"]
-	if found {
-		ref, err := extractSingleTargetRef(targetRef)
-		if err != nil {
-			return nil, err
-		}
-		ret = append(ret, ref)
+	var targetRefs egv1a1.PolicyTargetReferences
+	if err := json.Unmarshal(specAsJSON, &targetRefs); err != nil {
+		return nil, fmt.Errorf("no targets found for the policy")
 	}
+	ret := getPolicyTargetRefs(targetRefs, gateways)
 	if len(ret) == 0 {
 		return nil, fmt.Errorf("no targets found for the policy")
 	}
 	return ret, nil
 }
 
-func extractSingleTargetRef(data any) (gwapiv1a2.LocalPolicyTargetReferenceWithSectionName, error) {
-	var currRef gwapiv1a2.LocalPolicyTargetReferenceWithSectionName
-	d, err := json.Marshal(data)
-	if err != nil {
-		return currRef, err
-	}
-	if err := json.Unmarshal(d, &currRef); err != nil {
-		return currRef, err
-	}
-	if currRef.Group == "" || currRef.Name == "" || currRef.Kind == "" {
-		return currRef, fmt.Errorf("invalid targetRef found: %s", string(d))
-	}
-	return currRef, nil
-}
-
 func policyStatusToUnstructured(policyStatus gwapiv1a2.PolicyStatus) map[string]any {
 	ret := map[string]any{}
 	// No need to check the marshal/unmarshal error here
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index 269c968b4..f724a9485 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -542,7 +542,12 @@ func getPolicyTargetRefs[T client.Object](policy egv1a1.PolicyTargetReferences,
 	// to targets that were already found via the selectors. Only add them to the returned list if
 	// they are not yet there. Always add them at the end.
 	fastLookup := sets.New(ret...)
+	var emptyTargetRef gwapiv1a2.LocalPolicyTargetReferenceWithSectionName
 	for _, v := range policy.GetTargetRefs() {
+		if v == emptyTargetRef {
+			// This can happen when the targetRef structure is read from extension server policies
+			continue
+		}
 		if !fastLookup.Has(v) {
 			ret = append(ret, v)
 		}
diff --git a/release-notes/v1.1.0-rc.1.yaml b/release-notes/v1.1.0-rc.1.yaml
index 92f489a1d..ee5977ea5 100644
--- a/release-notes/v1.1.0-rc.1.yaml
+++ b/release-notes/v1.1.0-rc.1.yaml
@@ -130,6 +130,16 @@ changes:
 
     breaking-change: |
       Gateway-API BackendTLSPolicy v1alpha3 is incompatible with previous versions of the CRD
+      xPolicy targetRefs can no longer specify a namespace, since Gateway-API v1.1.0 uses LocalPolicyTargetReferenceWithSectionName in Policy resources
+
+
+    deprecation: |
+      xPolicy targetRef is deprecated, use targetRefs instead
+      SecurityPolicy ExtAuth BackendRef is deprecated, use BackendRefs instead
+      OpenTelemetry Proxy Access Log Host and Port are deprecated, use backendRefs instead
+      OpenTelemetry Proxy Metrics Sink Host and Port are deprecated, use backendRefs instead
+      Proxy Tracing Provider Host and Port are deprecated, use backendRefs instead
+      Envoy Gateway Extension Server Host and Port are deprecated, use BackendEndpoint instead
 
 
   - area: conformance
diff --git a/site/content/en/contributions/design/extending-envoy-gateway.md b/site/content/en/contributions/design/extending-envoy-gateway.md
index f7d3f3675..f3a970ef6 100644
--- a/site/content/en/contributions/design/extending-envoy-gateway.md
+++ b/site/content/en/contributions/design/extending-envoy-gateway.md
@@ -53,6 +53,10 @@ An example configuration:
 apiVersion: gateway.envoyproxy.io/v1alpha1
 kind: EnvoyGateway
 extensionManager:
+  poliyResources:
+  - group: example.myextension.io
+    version: v1alpha1
+    kind: ListenerPolicyKind
   resources:
   - group: example.myextension.io
     version: v2
@@ -65,15 +69,16 @@ extensionManager:
       - HTTPListener
       - Translation
   service:
-    host: my-extension.example
-    port: 443
+    fqdn: 
+      hostname: my-extension.example
+      port: 443
     tls:
       certificateRef:
         name: my-secret
         namespace: default
 ```
 
-An extension must supply connection information in the `extension.service` field so that Envoy Gateway can communicate with the extension. The `tls` configuration is optional.
+An extension must supply connection information in the `extension.service` field so that Envoy Gateway can communicate with the extension. The `tls` configuration is optional. Envoy Gateway supports connecting to an extension server either with TCP or with Unix Domain Sockets as a transport layer.
 
 If the extension wants Envoy Gateway to watch resources for it then the extension must configure the optional `extension.resources` field and supply a list of:
 
@@ -81,6 +86,15 @@ If the extension wants Envoy Gateway to watch resources for it then the extensio
 - `version`: the API version of the resource
 - `kind`: the Kind of resource
 
+If the extension wants Envoy Gateway to watch for policy resources then it must configure the optional `extensions.policyResources` field and supply a list of
+
+- `group`: the API group of the resource
+- `version`: the API version of the resource
+- `kind`: the Kind of resource
+
+Policy resources, like all Gateway-API policies, must contain `targetRef` or `targetRefs` fields in the spec which allow Envoy Gateway to identify which resources are targeted by the policy. 
+Policies can currently only target `Gateway` resources, and are provided as context to calls to the `HTTPListener` hook.
+
 The extension can configure the `extensionManager.hooks` field to specify which hook points it would like to support. If a given hook is not listed here then it will not be executed even
 if the extension is configured properly. This allows extension developers to only opt-in to the hook points they want to make use of.
 
@@ -93,8 +107,8 @@ Envoy Gateway manages [Envoy][] deployments, which act as the data plane that ha
 Gateway converts into [Envoy specific configuration (xDS)][] to send over to Envoy.
 
 Gateway API offers [ExtensionRef filters][] and [Policy Attachments][] as extension points for implementers to use. Envoy Gateway extends the Gateway API using these extension points to provide support for [rate limiting][]
-and [authentication][] native to the project. The initial design of Envoy Gateway extensions will primarily focus on ExtensionRef filters so that extension developers can reference their own resources as HTTP Filters in the same way
-that Envoy Gateway has native support for rate limiting and authentication filters.
+and [authentication][] native to the project. The initial design of Envoy Gateway extensions will primarily focus on `ExtensionRef` filters so that extension developers can reference their own resources as HTTP Filters in the same way
+that Envoy Gateway has native support for rate limiting and authentication filters, as well as policy resources which can target `Gateway`s.
 
 When Envoy Gateway encounters an [HTTPRoute][] or [GRPCRoute][] that has an `ExtensionRef` `filter` with a `group` and `kind` that Envoy Gateway does not support, it will first
 check the registered extension to determine if it supports the referenced object before considering it a configuration error.
@@ -144,10 +158,11 @@ and provide an error to the user.
 the namespace of any `ExtensionRef` is the same as the namespace of the `HTTPRoute` or `GRPCRoute` it is attached to rather than treating the `name` field of an `ExtensionRef` as a `name.namespace` string.
 If Gateway API adds support for these fields then the design of the Envoy Gateway extensions will be updated to support them.
 
+Similarly, any registered policy resource that targets an `HTTPListener` will be sent to the `HTTPListener` hook as context.
+
 ## Watching New Resources
 
-Envoy Gateway will dynamically create new watches on resources introduced by the registered Extension. It does so by using the [controller-runtime][] to create new watches on [Unstructured][] resources that match the `version`s, `group`s, and `kind`s that the
-registered extension configured. When communicating with an extension, Envoy Gateway sends these Unstructured resources over to the extension. This eliminates the need for the extension to create its own watches which would have a strong chance of creating race conditions and reconciliation loops when resources change. When an extension receives the Unstructured resources from Envoy Gateway it can perform its own type validation on them. Currently we make the simplifying assumption that the registered extension's `Kinds` are filters referenced by `extensionRef` in `HTTPRouteFilter`s . Support for Policy attachments will be introduced at a later time.
+Envoy Gateway will dynamically create new watches on resources introduced by the registered Extension. It does so by using the [controller-runtime][] to create new watches on [Unstructured][] resources that match the `version`s, `group`s, and `kind`s that the registered extension configured. When communicating with an extension, Envoy Gateway sends these Unstructured resources over to the extension. This eliminates the need for the extension to create its own watches which would have a strong chance of creating race conditions and reconciliation loops when resources change. When an extension receives the Unstructured resources from Envoy Gateway it can perform its own type validation on them. Currently we make the simplifying assumption that the registered extension's `Kinds` are filters referenced by `extensionRef` in `HTTPRouteFilter`s . Policy attachments which target `Gateway` resources work in the same way. 
 
 ## xDS Hooks API
 
@@ -236,7 +251,11 @@ message PostHTTPListenerModifyRequest {
 // Empty for now but we can add fields to the context as use-cases are discovered without
 // breaking any clients that use the API
 // additional context information can be added to this message as more use-cases are discovered
-message PostHTTPListenerExtensionContext {}
+message PostHTTPListenerExtensionContext {
+    // Resources introduced by the extension that were used as extension server
+    // policies targeting the listener
+    repeated ExtensionResource extension_resources = 1;
+}
 
 // PostHTTPListenerModifyResponse is the expected response from an extension and contains a modified version of the Listener that was sent
 // If an extension returns a nil Listener then it will not be modified
@@ -302,7 +321,7 @@ whenever Envoy Gateway creates an instance of Envoy Proxy. An extension develope
 
 ## Known Challenges
 
-Extending Envoy Gateway by using an external extension server which makes use of hook points in Envoy Gateway does comes with a few trade-offs. One known trade-off is the impact of the time that it takes for the hook calls to be executed. Since an extension would make use of hook points in Envoy Gateway that use gRPC for communication, the time it takes to perform these requests could become a concern for some extension developers. One way to minimize the request time of the hook calls is to load the extension server as a sidecar to Envoy Gateway to minimize the impact of networking on the hook calls.
+Extending Envoy Gateway by using an external extension server which makes use of hook points in Envoy Gateway does comes with a few trade-offs. One known trade-off is the impact of the time that it takes for the hook calls to be executed. Since an extension would make use of hook points in Envoy Gateway that use gRPC for communication, the time it takes to perform these requests could become a concern for some extension developers. One way to minimize the request time of the hook calls is to load the extension server as a sidecar to Envoy Gateway using the Unix Local Domain transport to minimize the impact of networking on the hook calls.
 
 [official goals]: https://github.com/envoyproxy/gateway/blob/main/GOALS.md#extensibility
 [ExtensionRef filters]: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.LocalObjectReference
diff --git a/site/content/en/news/releases/notes/v1.1.0-rc.1.md b/site/content/en/news/releases/notes/v1.1.0-rc.1.md
index 1b6b8e863..af96ea31b 100644
--- a/site/content/en/news/releases/notes/v1.1.0-rc.1.md
+++ b/site/content/en/news/releases/notes/v1.1.0-rc.1.md
@@ -128,6 +128,15 @@ Date: July 8, 2024
 
 ### Breaking Changes
 - Gateway-API BackendTLSPolicy v1alpha3 is incompatible with previous versions of the CRD
+- xPolicy targetRefs can no longer specify a namespace, since Gateway-API v1.1.0 uses LocalPolicyTargetReferenceWithSectionName in Policy resources
+
+### Deprecations
+- xPolicy targetRef is deprecated, use targetRefs instead
+- SecurityPolicy ExtAuth BackendRef is deprecated, use BackendRefs instead
+- OpenTelemetry Proxy Access Log Host and Port are deprecated, use backendRefs instead
+- OpenTelemetry Proxy Metrics Sink Host and Port are deprecated, use backendRefs instead
+- Proxy Tracing Provider Host and Port are deprecated, use backendRefs instead
+- Envoy Gateway Extension Server Host and Port are deprecated, use BackendEndpoint instead
 
 ## Conformance
 - Added Supported Features to Gateway Class
diff --git a/tools/hack/create-cluster.sh b/tools/hack/create-cluster.sh
index 992ee5def..93160727c 100755
--- a/tools/hack/create-cluster.sh
+++ b/tools/hack/create-cluster.sh
@@ -24,6 +24,10 @@ for _ in $(seq 1 "${NUM_WORKERS}"); do
 done
 fi
 
+## Check if kind cluster already exists.
+if tools/bin/kind get clusters | grep -q "${CLUSTER_NAME}"; then
+  echo "Cluster ${CLUSTER_NAME} already exists."
+else
 ## Create kind cluster.
 if [[ -z "${KIND_NODE_TAG}" ]]; then
   cat << EOF | tools/bin/kind create cluster --name "${CLUSTER_NAME}" --config -
@@ -34,6 +38,8 @@ else
 ${KIND_CFG}
 EOF
 fi
+fi
+
 
 ## Install MetalLB.
 kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/"${METALLB_VERSION}"/config/manifests/metallb-native.yaml
diff --git a/tools/linter/yamllint/.yamllint b/tools/linter/yamllint/.yamllint
index 4a9e282a2..c4db6d13d 100644
--- a/tools/linter/yamllint/.yamllint
+++ b/tools/linter/yamllint/.yamllint
@@ -9,6 +9,7 @@ ignore: |
   charts/gateway-addons-helm/
   bin/install.yaml
   test/helm/
+  examples/extension-server/charts/extension-server
 
 rules:
   braces:
diff --git a/tools/make/docs.mk b/tools/make/docs.mk
index c55d68ec2..43afb9585 100644
--- a/tools/make/docs.mk
+++ b/tools/make/docs.mk
@@ -1,5 +1,6 @@
 DOCS_OUTPUT_DIR := site/public
 RELEASE_VERSIONS ?= $(foreach v,$(wildcard ${ROOT_DIR}/docs/*),$(notdir ${v}))
+LINKINATOR_IGNORE := "github.com githubusercontent.com example.com github.io _print v0.6.0 v0.5.0 v0.4.0 v0.3.0 v0.2.0"
 CLEAN_NODE_MODULES ?= true
 
 ##@ Docs
@@ -111,7 +112,7 @@ docs-check-links:
 	# github.com does not allow access too often, there are a lot of 429 errors
 	# TODO: find a way to remove github.com from ignore list
 	# TODO: example.com is not a valid domain, we should remove it from ignore list
-	linkinator site/public/ -r --concurrency 25 --skip "github.com example.com github.io _print v0.6.0 v0.5.0 v0.4.0 v0.3.0 v0.2.0"
+	linkinator site/public/ -r --concurrency 25 --skip $(LINKINATOR_IGNORE)
 
 release-notes-docs: $(tools/release-notes-docs)
 	@$(LOG_TARGET)
diff --git a/tools/make/kube.mk b/tools/make/kube.mk
index 8cdd29f69..bf841adf1 100644
--- a/tools/make/kube.mk
+++ b/tools/make/kube.mk
@@ -40,7 +40,7 @@ CONTROLLERGEN_OBJECT_FLAGS :=  object:headerFile="$(ROOT_DIR)/tools/boilerplate/
 .PHONY: manifests
 manifests: $(tools/controller-gen) generate-gwapi-manifests ## Generate WebhookConfiguration and CustomResourceDefinition objects.
 	@$(LOG_TARGET)
-	$(tools/controller-gen) crd:allowDangerousTypes=true paths="./..." output:crd:artifacts:config=charts/gateway-helm/crds/generated
+	$(tools/controller-gen) crd:allowDangerousTypes=true paths="./api/..." output:crd:artifacts:config=charts/gateway-helm/crds/generated
 
 .PHONY: generate-gwapi-manifests
 generate-gwapi-manifests:
diff --git a/tools/src/release-notes-docs/yml2md.py b/tools/src/release-notes-docs/yml2md.py
index 7b334a5cd..b3b31b672 100755
--- a/tools/src/release-notes-docs/yml2md.py
+++ b/tools/src/release-notes-docs/yml2md.py
@@ -55,6 +55,10 @@ def convert_yaml_to_markdown(input_yaml_file, output_markdown_path):
                 file.write("### Breaking Changes\n")
                 file.write(change_to_markdown(area['breaking-change']) + '\n\n')
 
+            if 'deprecation' in area:
+                file.write("### Deprecations\n")
+                file.write(change_to_markdown(area['deprecation']) + '\n\n')
+
     print("Markdown file '{}' has been generated.".format(output_markdown_file))
 
 if __name__ == "__main__":


Test patch:
diff --git a/internal/gatewayapi/extensionserverpolicy_test.go b/internal/gatewayapi/extensionserverpolicy_test.go
index 6837f7e5c..f6ebf8e36 100644
--- a/internal/gatewayapi/extensionserverpolicy_test.go
+++ b/internal/gatewayapi/extensionserverpolicy_test.go
@@ -10,7 +10,6 @@ import (
 
 	"github.com/stretchr/testify/require"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/utils/ptr"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 )
 
@@ -41,7 +40,7 @@ func TestExtractTargetRefs(t *testing.T) {
 				"targetRefs": "someValue",
 			},
 			output:        nil,
-			expectedError: "targetRefs is not an array",
+			expectedError: "no targets found for the policy",
 		},
 		{
 			desc: "invalid targetref",
@@ -51,7 +50,7 @@ func TestExtractTargetRefs(t *testing.T) {
 				},
 			},
 			output:        nil,
-			expectedError: "invalid targetRef found: {\"someKey\":\"someValue\"}",
+			expectedError: "no targets found for the policy",
 		},
 		{
 			desc: "valid single targetRef",
@@ -105,53 +104,6 @@ func TestExtractTargetRefs(t *testing.T) {
 				},
 			},
 		},
-		{
-			desc: "valid multiple targetRefs and targetRef",
-			specInput: map[string]any{
-				"targetRefs": []any{
-					map[string]any{
-						"group": "some.group",
-						"kind":  "SomeKind2",
-						"name":  "othername",
-					},
-					map[string]any{
-						"group": "some.group",
-						"kind":  "SomeKind",
-						"name":  "name",
-					},
-				},
-				"targetRef": map[string]any{
-					"group":       "some.group",
-					"kind":        "SomeKind",
-					"name":        "three",
-					"sectionName": "one",
-				},
-			},
-			output: []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
-				{
-					LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
-						Group: "some.group",
-						Kind:  "SomeKind2",
-						Name:  "othername",
-					},
-				},
-				{
-					LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
-						Group: "some.group",
-						Kind:  "SomeKind",
-						Name:  "name",
-					},
-				},
-				{
-					LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
-						Group: "some.group",
-						Kind:  "SomeKind",
-						Name:  "three",
-					},
-					SectionName: ptr.To(gwapiv1a2.SectionName("one")),
-				},
-			},
-		},
 	}
 
 	for _, currTest := range tests {
@@ -160,7 +112,7 @@ func TestExtractTargetRefs(t *testing.T) {
 				Object: map[string]any{},
 			}
 			policy.Object["spec"] = currTest.specInput
-			targets, err := extractTargetRefs(policy)
+			targets, err := extractTargetRefs(policy, []*GatewayContext{})
 
 			if currTest.expectedError != "" {
 				require.EqualError(t, err, currTest.expectedError)
diff --git a/site/content/en/latest/tasks/extensibility/extension-server.md b/site/content/en/latest/tasks/extensibility/extension-server.md
new file mode 100644
index 000000000..7d67c23f6
--- /dev/null
+++ b/site/content/en/latest/tasks/extensibility/extension-server.md
@@ -0,0 +1,209 @@
+---
+title: "Envoy Gateway Extension Server"
+---
+
+This task explains how to extend Envoy Gateway using an Extension Server. Envoy Gateway
+can be configured to call an external server over gRPC with the xDS configuration _before_ 
+it is sent to Envoy Proxy. The external server can modify the provided configuration 
+programmatically using any semantics supported by the [xDS][] API.
+
+Using an extension server allows vendors to add xDS configuration that Envoy Gateway itself
+doesn't support with a very high level of control over the generated xDS configuration.
+
+**Note:** Modifying the xDS configuration generated by Envoy Gateway may break functionality
+configured by native Envoy Gateway means. Like other cases where the xDS configuration
+is modified outside of Envoy Gateway's control, this is risky and should be tested thoroughly,
+especially when using the same extension server across different Envoy Gateway versions.
+
+## Introduction
+
+One of the Envoy Gateway project goals is to "provide a common foundation for vendors to 
+build value-added products without having to re-engineer fundamental interactions". The
+Envoy Gateway Extension Server provides a mechanism where Envoy Gateway tracks all provider
+resources and then calls a set of hooks that allow the generated xDS configuration to be
+modified before it is sent to Envoy Proxy. See the [design documentation][] for full details.
+
+This task sets up an example extension server that adds the Envoy Proxy Basic Authentication
+HTTP filter to all the listeners generated by Envoy Gateway. The example extension server 
+includes its own CRD which allows defining username/password pairs that will be accepted by
+the Envoy Proxy. 
+
+**Note:** Envoy Gateway supports adding Basic Authentication to routes using a [SecurityPolicy][].
+See [this task](../security/basic-auth) for the preferred way to configure Basic 
+Authentication.
+
+
+## Quickstart
+
+### Prerequisites
+
+* Follow the steps from the [Quickstart](../quickstart) task to install Envoy Gateway and the example manifest.
+  Before proceeding, you should be able to query the example backend using HTTP.
+
+### Build and run the example Extension Server
+
+Build and deploy the example extension server in the `examples/extension-server` folder into the cluster 
+running Envoy Gateway.
+
+* Build the extension server image
+
+  **Note:** The provided `Makefile` builds an image with the name `extension-server:latest`. You may need to create
+a different tag for it in order to allow Kubernetes to pull it correctly.
+
+  ```shell
+  make image
+  ```
+
+* Publish the extension server image in your docker repository
+
+  {{< tabpane text=true >}}
+  {{% tab header="local kind server" %}}
+
+  ```shell
+  kind load docker-image --name envoy-gateway extension-server:latest
+  ```
+
+  {{% /tab %}}
+  {{% tab header="other Kubernetes server" %}}
+
+  ```shell
+  docker tag extension-server:latest $YOUR_DOCKER_REPO
+  docker push $YOUR_DOCKER_REPO
+  ```
+
+  {{% /tab %}}
+  {{< /tabpane >}}
+
+* Deploy the extension server in your cluster
+
+  If you are using your own docker image repository, make sure to update the `values.yaml` with the correct
+image name and tag.
+
+  ```shell
+  helm install -n envoy-gateway-system extension-server ./examples/extension-server/charts/extension-server
+  ```
+
+### Configure Envoy Gateway
+
+* Grant Envoy Gateway's `ServiceAccount` permission to access the extension server's CRD
+
+  ```shell
+  kubectl create clusterrole listener-context-example-viewer \
+             --verb=get,list,watch  \
+             --resource=ListenerContextExample
+
+  kubectl create clusterrolebinding envoy-gateway-listener-context \
+             --clusterrole=listener-context-example-viewer \
+             --serviceaccount=envoy-gateway-system:envoy-gateway
+  ```
+
+* Configure Envoy Gateway to use the Extension Server
+
+  Add the following fragment to Envoy Gateway's [configuration][] file:
+
+  ```yaml
+  extensionManager:
+    # Envoy Gateway will watch these resource kinds and use them as extension policies
+    # which can be attached to Gateway resources.
+    policyResources:
+    - group: example.extensions.io
+      version: v1alpha1
+      kind: ListenerContextExample
+    hooks:
+      # The type of hooks that should be invoked
+      xdsTranslator:
+        post:
+        - HTTPListener
+    service:
+      # The service that is hosting the extension server
+      fqdn: 
+        hostname: extension-server.envoy-gateway-system.svc.cluster.local
+        port: 5005
+  ```
+
+  After updating Envoy Gateway's configuration file, restart Envoy Gateway.
+
+## Testing
+
+Get the Gateway's address:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+The extension server adds the Basic Authentication HTTP filter to all listeners configured by 
+Envoy Gateway. Initially there are no valid user/password combinations available. Accessing the
+example backend should fail with a 401 status:
+
+```console
+$ curl -v --header "Host: www.example.com" "http://${GATEWAY_HOST}/example"
+...
+> GET /example HTTP/1.1
+> Host: www.example.com
+> User-Agent: curl/7.81.0
+> Accept: */*
+> 
+* Mark bundle as not supporting multiuse
+< HTTP/1.1 401 Unauthorized
+< www-authenticate: Basic realm="http://www.example.com/example"
+< content-length: 58
+< content-type: text/plain
+< date: Mon, 08 Jul 2024 10:53:11 GMT
+< 
+...
+User authentication failed. Missing username and password.
+...
+```
+
+Add a new Username/Password combination using the example extension server's CRD:
+
+```shell
+kubectl apply -f - << EOF 
+apiVersion: example.extensions.io/v1alpha1
+kind: ListenerContextExample
+metadata:
+  name: listeneruser
+spec:
+  targetRefs:
+  - kind: Gateway
+    name: eg
+    group: gateway.networking.k8s.io
+  username: user
+  password: p@ssw0rd
+EOF
+```
+
+Authenticating with this user/password combination will now work.
+
+```console
+$ curl -v http://${GATEWAY_HOST}/example  -H "Host: www.example.com"   --user 'user:p@ssw0rd'
+...
+> GET /example HTTP/1.1
+> Host: www.example.com
+> Authorization: Basic dXNlcm5hbWU6cEBzc3cwcmQ=
+> User-Agent: curl/7.81.0
+> Accept: */*
+> 
+* Mark bundle as not supporting multiuse
+< HTTP/1.1 200 OK
+< content-type: application/json
+< x-content-type-options: nosniff
+< date: Mon, 08 Jul 2024 10:56:17 GMT
+< content-length: 559
+< 
+...
+ "headers": {
+  "Authorization": [
+   "Basic dXNlcm5hbWU6cEBzc3cwcmQ="
+  ],
+  "X-Example-Ext": [
+   "user"
+  ],
+...
+```
+
+
+[xDS]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration
+[design documentation]: /contributions/design/extending-envoy-gateway
+[SecurityPolicy]: /latest/api/extension_types/#securitypolicy
+[configuration]: /latest/api/extension_types/#extensionmanager
diff --git a/site/content/en/latest/tasks/observability/proxy-accesslog.md b/site/content/en/latest/tasks/observability/proxy-accesslog.md
index 02596c946..fb0200f17 100644
--- a/site/content/en/latest/tasks/observability/proxy-accesslog.md
+++ b/site/content/en/latest/tasks/observability/proxy-accesslog.md
@@ -140,6 +140,57 @@ Verify logs from loki:
 curl -s "http://$LOKI_IP:3100/loki/api/v1/query_range" --data-urlencode "query={exporter=\"OTLP\"}" | jq '.data.result[0].values'
 ```
 
+## gGRPC Access Log Service(ALS) Sink
+
+Envoy Gateway can send logs to a backend implemented [gRPC access log service proto](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/accesslog/v3/als.proto).
+There's an example service [here](https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/envoy-als.yaml), which simply count the log and export to prometheus endpoint.
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/envoy-als.yaml -n monitoring
+```
+
+The following configuration sends logs to the gRPC access log service:
+
+```shell
+kubectl apply -f - <<EOF
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: eg
+spec:
+  controllerName: gateway.envoyproxy.io/gatewayclass-controller
+  parametersRef:
+    group: gateway.envoyproxy.io
+    kind: EnvoyProxy
+    name: als
+    namespace: envoy-gateway-system
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: als
+  namespace: envoy-gateway-system
+spec:
+  telemetry:
+    accessLog:
+      settings:
+        - sinks:
+            - type: ALS
+              als:
+                backendRefs:
+                  - name: envoy-als
+                    namespace: monitoring
+                    port: 8080
+                type: HTTP
+EOF
+```
+
+Verify logs from envoy-als:
+
+```shell
+curl -s "http://$(kubectl get svc envoy-als -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):19001/metrics" | grep log_count
+```
+
 ## CEL Expressions
 
 Envoy Gateway provides [CEL expressions](https://www.envoyproxy.io/docs/envoy/latest/xds/type/v3/cel.proto.html#common-expression-language-cel-proto) to filter access log . 
@@ -197,4 +248,4 @@ curl -s "http://$LOKI_IP:3100/loki/api/v1/query_range" --data-urlencode "query={
 Envoy Gateway provides additional metadata about the K8s resources that were translated to  certain envoy resources.
 For example, details about the `HTTPRoute` and `GRPCRoute` (kind, group, name, namespace and annotations) are available
 for access log formatter using the `METADATA` operator. To enrich logs, users can add log operator such as:
-`%METADATA(ROUTE:envoy-gateway:resources)%` to their access log format. 
\ No newline at end of file
+`%METADATA(ROUTE:envoy-gateway:resources)%` to their access log format. 
diff --git a/site/content/en/latest/tasks/quickstart.md b/site/content/en/latest/tasks/quickstart.md
index 93458a4eb..4075eb5d4 100644
--- a/site/content/en/latest/tasks/quickstart.md
+++ b/site/content/en/latest/tasks/quickstart.md
@@ -110,6 +110,8 @@ kubectl apply -f ./gateway-helm/crds/generated
 
 Update your `BackendTLSPolicy` and `GRPCRoute` resources according to Gateway-API [v1.1 Upgrade Notes](https://gateway-api.sigs.k8s.io/guides/#v11-upgrade-notes)
 
+Update your Envoy Gateway xPolicy resources: remove the namespace section from targetRef. 
+
 Install Envoy Gateway v1.1.0:
 
 ```shell
diff --git a/site/content/en/latest/tasks/security/restrict-ip-access.md b/site/content/en/latest/tasks/security/restrict-ip-access.md
new file mode 100644
index 000000000..ba6af1182
--- /dev/null
+++ b/site/content/en/latest/tasks/security/restrict-ip-access.md
@@ -0,0 +1,197 @@
+---
+title: "IP Allowlist/Denylist"
+---
+
+This task provides instructions for configuring IP allowlist/denylist on Envoy Gateway. IP allowlist/denylist
+checks if an incoming request is from an allowed IP address before routing the request to a backend service.
+
+Envoy Gateway introduces a new CRD called [SecurityPolicy][SecurityPolicy] that allows the user to configure IP allowlist/denylist.
+This instantiated resource can be linked to a [Gateway][Gateway], [HTTPRoute][HTTPRoute] or [GRPCRoute][GRPCRoute] resource.
+
+## 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.
+
+## Configuration
+
+### Create a SecurityPolicy
+
+The below SecurityPolicy restricts access to the backend service by allowing requests only from the IP addresses `10.0.1.0/24`. 
+
+In this example, the default action is set to `Deny`, which means that only requests from the specified IP addresses with `Allow`
+action are allowed, and all other requests are denied. You can also change the default action to `Allow` to allow all requests 
+except those from the specified IP addresses with `Deny` action.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-client-ip
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: backend
+  authorization:
+    defaultAction: Deny
+    rules:
+    - action: Allow
+      principal:
+        clientCIDRs:
+        - 10.0.1.0/24
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-client-ip
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: backend
+  authorization:
+    defaultAction: Deny
+    rules:
+      - action: Allow
+        principal:
+          clientCIDRs:
+            - 10.0.1.0/24
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+Verify the SecurityPolicy configuration:
+
+```shell
+kubectl get securitypolicy/authorization-client-ip -o yaml
+```
+
+### Original Source IP
+
+It's important to note that the IP address used for allowlist/denylist is the original source IP address of the request.
+You can use a [ClientTrafficPolicy] to configure how Envoy Gateway should determine the original source IP address.
+
+For example, the below ClientTrafficPolicy configures Envoy Gateway to use the `X-Forwarded-For` header to determine the original source IP address.
+The `numTrustedHops` field specifies the number of trusted hops in the `X-Forwarded-For` header. In this example, the `numTrustedHops` is set to `1`,
+which means that the first rightmost IP address in the `X-Forwarded-For` header is used as the original source IP address.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: ClientTrafficPolicy
+metadata:
+  name: enable-client-ip-detection
+spec:
+  clientIPDetection:
+    xForwardedFor:
+      numTrustedHops: 1
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: Gateway
+    name: eg
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: ClientTrafficPolicy
+metadata:
+  name: enable-client-ip-detection
+spec:
+  clientIPDetection:
+    xForwardedFor:
+      numTrustedHops: 1
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: Gateway
+    name: eg
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+
+## Testing
+
+Ensure the `GATEWAY_HOST` environment variable from the [Quickstart](../../quickstart) is set. If not, follow the
+Quickstart instructions to set the variable.
+
+```shell
+echo $GATEWAY_HOST
+```
+
+Send a request to the backend service without the `X-Forwarded-For` header:
+
+```shell
+curl -v -H "Host: www.example.com" "http://${GATEWAY_HOST}/"
+```
+
+You should see `403 Forbidden` in the response, indicating that the request is not allowed.
+
+```shell
+* Connected to 172.18.255.200 (172.18.255.200) port 80
+> GET /get HTTP/1.1
+> Host: www.example.com
+> User-Agent: curl/8.8.0-DEV
+> Accept: */*
+> 
+* Request completely sent off
+< HTTP/1.1 403 Forbidden
+< content-length: 19
+< content-type: text/plain
+< date: Mon, 08 Jul 2024 04:23:31 GMT
+< 
+* Connection #0 to host 172.18.255.200 left intact
+RBAC: access denied
+```
+
+Send a request to the backend service with the `X-Forwarded-For` header:
+
+```shell
+curl -v -H "Host: www.example.com" -H "X-Forwarded-For: 10.0.1.1" "http://${GATEWAY_HOST}/"
+```
+
+The request should be allowed and you should see the response from the backend service.
+
+## Clean-Up
+
+Follow the steps from the [Quickstart](../../quickstart) to uninstall Envoy Gateway and the example manifest.
+
+Delete the SecurityPolicy and the ClientTrafficPolicy
+
+```shell
+kubectl delete securitypolicy/authorization-client-ip
+kubectl delete clientTrafficPolicy/enable-client-ip-detection
+```
+
+## Next Steps
+
+Checkout the [Developer Guide](../../../contributions/develop) to get involved in the project.
+
+[SecurityPolicy]: ../../../contributions/design/security-policy
+[ClientTrafficPolicy]: ../../../api/extension_types#clienttrafficpolicy
+[Gateway]: https://gateway-api.sigs.k8s.io/api-types/gateway
+[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute
+[GRPCRoute]: https://gateway-api.sigs.k8s.io/api-types/grpcroute