diff --git a/docs/cloud/latest/cloud-features.yml b/docs/cloud/latest/cloud-features.yml
index b36d463a8..35d38c679 100644
--- a/docs/cloud/latest/cloud-features.yml
+++ b/docs/cloud/latest/cloud-features.yml
@@ -1,9 +1,5 @@
# Cloud features should be represented as objects, path always starting with '/' and ending without '/'.
-- {
- path: /docs/cloud/latest/onboarding-dashboard,
- feature: Team collaboration,
- }
- {
path: /docs/cloud/latest/service-catalog/concepts/services,
feature: Team collaboration,
@@ -20,10 +16,6 @@
path: /docs/cloud/latest/service-catalog/howtos/cells,
feature: Team collaboration,
}
-- {
- path: /docs/cloud/latest/onboarding-dashboard,
- feature: Team collaboration,
- }
- {
path: /docs/cloud/latest/service-catalog/howtos/intercept,
diff --git a/docs/cloud/latest/images/onboarding-dashboard.png b/docs/cloud/latest/images/onboarding-dashboard.png
index 506995c98..2b0f38349 100644
Binary files a/docs/cloud/latest/images/onboarding-dashboard.png and b/docs/cloud/latest/images/onboarding-dashboard.png differ
diff --git a/docs/cloud/latest/onboarding-dashboard/index.md b/docs/cloud/latest/onboarding-dashboard/index.md
index cc42be08e..1658834dd 100644
--- a/docs/cloud/latest/onboarding-dashboard/index.md
+++ b/docs/cloud/latest/onboarding-dashboard/index.md
@@ -5,26 +5,24 @@ description: "In this section, you can see the ambassador onboarding procces of
# Onboarding Dashboard
-A central location to see the status of your developers, and the services within your organization.
+A central location to check the status of your developers, or the count of telepresence connections to your clusters.
## Onboarding Overview
-All the members of your organization can look at the onboarding dashboard to see which invited developers haven't onboarded and which have. You can also invite new developers here, or see which services are in use across your organization.
+All members of your organization have the ability to access the dashboard, where they can clearly identify invited developers who have not yet joined, as well as those who have. Furthermore, from this dashboard, you can send invitations to new developers (excluding Developer and Lite subscriptions).
+In addition to this, easy access is provided to information about your clusters, as well as the features developed by Ambassador.
-
-
-
-
-
-
+- **Connects:** Total telepresence connections made to your clusters.
+- **Clusters:** Your clusters set up through Ambassador Cloud.
+- **Features:** Additional functionalities that Ambassador provides for you and your organization.
+Team members section is available for all subscriptions except Lite and Developer:
- **Invite more people:** Invite your colleagues to collaborate, send an email invitation link to start the Ambassador Cloud experience.
- **Re-Invite people:** Resend the invite to colleagues that didn't accept the first invite.
- **Developers onboarded:** See the number of people that have created or initialized an intercept.
- **Need to be onboarded:** List of people who have not created any intercept. You can nudge them to send some useful email with instructions about how to start.
-- **Active services:** Your services that are currently flagged as `active`, how they were activated, and the last seen date.
diff --git a/docs/cloud/latest/release-notes/new-organization-dashboard.png b/docs/cloud/latest/release-notes/new-organization-dashboard.png
new file mode 100644
index 000000000..1ac414c67
Binary files /dev/null and b/docs/cloud/latest/release-notes/new-organization-dashboard.png differ
diff --git a/docs/cloud/latest/releaseNotes.yml b/docs/cloud/latest/releaseNotes.yml
index a6bc3170b..8dfe86c9c 100644
--- a/docs/cloud/latest/releaseNotes.yml
+++ b/docs/cloud/latest/releaseNotes.yml
@@ -30,6 +30,12 @@
changelog: ''
items:
+ - date: '2023-10-02'
+ notes:
+ - title: 'Organization Dashboard redesign'
+ body: The Dashboard was redesigned to better highlight users and organization's usage of Telepresence and Edge Stack.
+ image: './new-organization-dashboard.png'
+ type: feature
- date: '2023-08-29'
notes:
- title: 'New subscription plans'
diff --git a/docs/edge-stack/2.5/topics/install/helm.md b/docs/edge-stack/2.5/topics/install/helm.md
index 89140893f..f814e3fa9 100644
--- a/docs/edge-stack/2.5/topics/install/helm.md
+++ b/docs/edge-stack/2.5/topics/install/helm.md
@@ -62,7 +62,7 @@ When you run the Helm chart, it installs $productName$.
```
helm install -n $productNamespace$ --create-namespace \
- $productHelmName$ datawire/$productHelmName$ && \
+ $productHelmName$ datawire/$productHelmName$ --version $aesChartVersion$ && \
kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
```
diff --git a/docs/edge-stack/2.5/tutorials/getting-started.md b/docs/edge-stack/2.5/tutorials/getting-started.md
index 3c8b82c21..9239192db 100644
--- a/docs/edge-stack/2.5/tutorials/getting-started.md
+++ b/docs/edge-stack/2.5/tutorials/getting-started.md
@@ -23,11 +23,11 @@ We'll start by installing $productName$ into your cluster.
**We recommend using Helm** but there are other options below to choose from.
-
+
### Connecting your installation to Ambassador Cloud
-Now is a great time to enhance your $productName$ experience and take advantage of Ambassador Cloud's advanced capabilities.
+Now is a great time to enhance your $productName$ experience and take advantage of Ambassador Cloud's advanced capabilities.
1. Log in to [Ambassador Cloud](https://app.getambassador.io/cloud/services/) with GitHub, GitLab or Google and select your team account.
diff --git a/docs/edge-stack/2.5/tutorials/gs-tabs.js b/docs/edge-stack/2.5/tutorials/gs-tabs.js
index 0f8e0985d..43b4fb911 100644
--- a/docs/edge-stack/2.5/tutorials/gs-tabs.js
+++ b/docs/edge-stack/2.5/tutorials/gs-tabs.js
@@ -47,6 +47,7 @@ const useStyles = makeStyles((theme) => ({
export default function GettingStartedEdgeStack21Tabs(props) {
const version = props.version;
+ const aesChartVersion = props.aesChartVersion;
const classes = useStyles();
const [value, setValue] = React.useState(0);
@@ -103,7 +104,7 @@ export default function GettingStartedEdgeStack21Tabs(props) {
'\n' +
'kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system' +
'\n' +
- 'helm install edge-stack --namespace ambassador datawire/edge-stack && \\' +
+ `helm install edge-stack --namespace ambassador datawire/edge-stack --version ${aesChartVersion} && \\` +
'\n' +
'kubectl -n ambassador wait --for condition=available --timeout=90s deploy -lproduct=aes'}
diff --git a/docs/edge-stack/3.7/versions.yml b/docs/edge-stack/3.7/versions.yml
index 045cbca3c..94ff8e922 100644
--- a/docs/edge-stack/3.7/versions.yml
+++ b/docs/edge-stack/3.7/versions.yml
@@ -11,14 +11,14 @@ productHelmName: edge-stack
# OSS (not self)
ossVersion: 3.7.2
-ossDocsVersion: "pre-release"
+ossDocsVersion: "3.7"
ossChartVersion: 8.7.2
OSSproductName: "Emissary-ingress"
OSSproductNamePlural: "Emissary-ingresses"
# AES (self)
aesVersion: 3.7.2
-aesDocsVersion: "pre-release"
+aesDocsVersion: "3.7"
aesChartVersion: 8.7.2
AESproductName: "Ambassador Edge Stack"
AESproductNamePlural: "Ambassador Edge Stacks"
diff --git a/docs/edge-stack/3.8/releaseNotes.yml b/docs/edge-stack/3.8/releaseNotes.yml
index b11e72c9a..a5c68b973 100644
--- a/docs/edge-stack/3.8/releaseNotes.yml
+++ b/docs/edge-stack/3.8/releaseNotes.yml
@@ -32,6 +32,21 @@
changelog: https://github.com/datawire/edge-stack/blob/$branch$/CHANGELOG.md
items:
+ - version: 3.8.2
+ date: '2023-10-11'
+ notes:
+ - title: Upgrade Envoy
+ type: security
+ body: >-
+ This release includes security patches to the current Envoy proxy version to address CVE 2023-44487 and includes a fix to determine if a client is making too many requests with premature resets. The connection is disconnected if more than 50 percent of resets are considered premature. Another fix is also included which exposes a runtime setting to control the limit on the number of HTTP requests processed from a single connection in a single I/O cycle to mitigate CPU starvation.
+ docs: topics/running/running/
+
+ - title: Upgrade Golang to 1.20.10
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This update resolves CVE-2023-39323 and CVE-2023-39325.
+ docs: https://go.dev/doc/devel/release#go1.20.minor
+
- version: 3.8.1
date: '2023-09-18'
notes:
diff --git a/docs/edge-stack/3.8/versions.yml b/docs/edge-stack/3.8/versions.yml
index df6d9c9b1..7691f0016 100644
--- a/docs/edge-stack/3.8/versions.yml
+++ b/docs/edge-stack/3.8/versions.yml
@@ -2,7 +2,7 @@
branch: release/v3.8
# self
-version: 3.8.1
+version: 3.8.2
productName: "Ambassador Edge Stack"
productNamePlural: "Ambassador Edge Stacks"
productNamespace: ambassador
@@ -10,16 +10,16 @@ productDeploymentName: edge-stack
productHelmName: edge-stack
# OSS (not self)
-ossVersion: 3.8.1
-ossDocsVersion: "pre-release"
-ossChartVersion: 8.8.1
+ossVersion: 3.8.2
+ossDocsVersion: "3.8"
+ossChartVersion: 8.8.2
OSSproductName: "Emissary-ingress"
OSSproductNamePlural: "Emissary-ingresses"
# AES (self)
-aesVersion: 3.8.1
-aesDocsVersion: "pre-release"
-aesChartVersion: 8.8.1
+aesVersion: 3.8.2
+aesDocsVersion: "3.8"
+aesChartVersion: 8.8.2
AESproductName: "Ambassador Edge Stack"
AESproductNamePlural: "Ambassador Edge Stacks"
diff --git a/docs/edge-stack/3.9/about/aes-emissary-eol.md b/docs/edge-stack/3.9/about/aes-emissary-eol.md
new file mode 100644
index 000000000..1e4b2caa9
--- /dev/null
+++ b/docs/edge-stack/3.9/about/aes-emissary-eol.md
@@ -0,0 +1,56 @@
+# $productName$ End of Life Policy
+
+This document describes the End of Life policy and maintenance windows for Ambassador Edge Stack, and to the open source project Emissary Ingress.
+
+## Supported Versions
+
+Ambassador Edge Stack and Emissary-ingress versions are expressed as **x.y.z**, where **x** is the major version, **y** is the minor version, and **z** is the patch version, following [Semantic Versioning](https://semver.org/) terminology.
+
+**X-series (Major Versions)**
+
+- **1.y**: 1.0 GA on January 2020
+- **2.y**: 2.0.4 GA on October 2021, and 2.1.0 in December 2021.
+
+**Y-release (Minor versions)**
+
+- For 1.y, that is **1.14.z**
+- For 2.y, that is **2.3.z**
+
+In this document, **Current** refers to the latest X-series release.
+
+Maintenance refers to the previous X-series release, including security and Sev1 defect patches.
+
+## CNCF Ecosystem Considerations
+
+- Envoy releases a major version every 3 months and supports its previous releases for 12 months. Envoy does not support any release longer than 12 months.
+- Kubernetes 1.19 and newer receive 12 months of patch support (The [Kubernetes Yearly Support Period](https://github.com/kubernetes/enhancements/blob/master/keps/sig-release/1498-kubernetes-yearly-support-period/README.md)).
+
+# The Policy
+
+> We will offer a 6 month maintenance window for the latest Y-release of an X-series after a new X-series goes GA and becomes the current release. For example, we will support 2.3 for severity 1 and defect patches for six months after 3.0 is released.
+>
+
+> During the maintenance window, Y-releases will only receive security and Sev1 defect patches. Users desiring new features or bug fixes for lower severity defects will need to upgrade to the current X-series.
+>
+
+> The current X-series will receive as many Y-releases as necessary and as often as we have new features or patches to release.
+>
+
+> Ambassador Labs offers no-downtime migration to current versions from maintenance releases. Migration from releases that are outside of the maintenance window may be subject to downtime.
+>
+
+> Artifacts of releases outside of the maintenance window will be frozen and will remain available publicly for download with the best effort. These artifacts include Docker images, application binaries, Helm charts, etc.
+>
+
+### When we say support with “defect patches”, what do we mean?
+
+- We will fix security issues in our Emissary-ingress and Ambassador Edge Stack code
+- We will pick up security fixes from dependencies as they are made available
+- We will not maintain forks of our major dependencies
+- We will not attempt our own back ports of critical fixes to dependencies which are out of support from their own communities
+
+## Extended Maintenance for 1.14
+
+Given this policy, we should have dropped maintenance for 1.14 in March 2022, however we recognize that the introduction of an EOL policy necessitates a longer maintenance window. For this reason, we do offer an "extended maintenance" window for 1.14 until the end of September 2022, 3 months after the latest 2.3 release. Please note that this extended maintenance window will not apply to customers using Kubernetes 1.22 and above, and this extended maintenance will also not provide a no-downtime migration path from 1.14 to 3.0.
+
+After September 2022, the current series will be 3.x, and the maintenance series will be 2.y.
diff --git a/docs/edge-stack/3.9/about/changes-2.x.md b/docs/edge-stack/3.9/about/changes-2.x.md
new file mode 100644
index 000000000..89938a44b
--- /dev/null
+++ b/docs/edge-stack/3.9/about/changes-2.x.md
@@ -0,0 +1,243 @@
+import Alert from '@material-ui/lab/Alert';
+
+Major Changes in $productName$ 2.X
+==================================
+
+The 2.X family introduces a number of changes to allow $productName$
+to more gracefully handle larger installations, reduce global configuration to
+better handle multitenant or multiorganizational installations, reduce memory
+footprint, and improve performance. We welcome feedback!! Join us on
+[Slack](http://a8r.io/slack) and let us know what you think.
+
+While $productName$ 2 is functionally compatible with $productName$ 1.14, note
+that this is a **major version change** and there are important differences between
+$productName$ 1.X and $productName$ $version$. For details, read on.
+
+## 1. Configuration API Version `getambassador.io/v3alpha1`
+
+$productName$ 2.0 introduced API version `getambassador.io/v3alpha1` to allow
+certain changes in configuration resources that are not backwards compatible with
+$productName$ 1.X. The most notable example of change is the addition of the
+**mandatory** `Listener` resource; however, there are important changes
+in `Host` and `Mapping` as well.
+
+
+ $productName$ 2.X supports only API versions getambassador.io/v2
+ and getambassador.io/v3alpha1. If you are using any resources with
+ older API versions, you will need to upgrade them.
+
+
+API version `getambassador.io/v3alpha1` replaces `x.getambassador.io/v3alpha1` from
+the 2.0 developer previews. `getambassador.io/v3alpha1` may still change as we receive
+feedback.
+
+## 2. Kubernetes 1.22 and Structural CRDs
+
+Kubernetes 1.22 requires [structural CRDs](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/).
+This change is primarily meant to support better CRD validation, but it also has the
+effect that union types are no longer allowed in CRDs: for example, an element that can be
+either a string or a list of strings is not allowed. Several such elements appeared in the
+`getambassador.io/v2` CRDs, requiring changes. In `getambassador.io/v3alpha1`:
+
+- `ambassador_id` must always be a list of strings
+- `Host.mappingSelector` supersedes `Host.selector`, and controls association between Hosts and Mappings
+- `Mapping.hostname` supersedes `Mapping.host` and `Mapping.host_regex`
+- `Mapping.tls` can only be a string
+- `Mapping.labels` always requires maps instead of strings
+
+## 2. `Listener`s, `Host`s, and `Mapping`s
+
+$productName$ 2.0 introduced the new **mandatory** `Listener` CRD, and made some changes
+to the `Host` and `Mapping` resources.
+
+### The `Listener` CRD
+
+The new [`Listener` CRD](../../topics/running/listener) defines where and how $productName$ should listen for requests from the network, and which `Host` definitions should be used to process those requests.
+
+**Note that `Listener`s are never created by $productName$, and must be defined by the user.** If you do not
+define any `Listener`s, $productName$ will not listen anywhere for connections, and therefore won't do
+anything useful. It will log a `WARNING` to this effect.
+
+A `Listener` specifically defines
+
+- `port`: a port number on which to listen for new requests;
+- `protocol` and `securityModel`: the protocol stack and security model to use (e.g. `HTTPS` using the `X-Forwarded-Proto` header); and
+- `hostBinding`: how to tell if a given `Host` should be associated with this `Listener`:
+ - a `Listener` can choose to consider all `Host`s, or only `Host`s in the same namespace as the `Listener`, or
+ - a `Listener` can choose to consider only `Host`s with a particular Kubernetes `label`.
+
+**Note that the `hostBinding ` is mandatory.** A `Listener` _must_ specify how to identify the `Host`s to associate with the `Listener`', or the `Listener` will be rejected. This is intended to help prevent cases where a `Listener` mistakenly grabs too many `Host`s: if you truly need a `Listener` that associates with all `Host`s, the easiest way is to tell the `Listener` to look for `Host`s in all namespaces, with no further selectors, for example:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: listener
+metadata:
+ name: all-hosts-listener
+spec:
+ port: 8080
+ securityModel: XFP
+ protocol: HTTPS
+ hostBinding:
+ namespace:
+ from: ALL
+```
+
+A `Listener` that has no associated `Host`s will be logged as a `WARNING`, and will not be included in the Envoy configuration generated by $productName$.
+
+Note also that there is no limit on how many `Listener`s may be created, and as such no limit on the number of ports to which a `Host` may be associated.
+
+
+ Learn more about Listener.
+ Learn more about Host.
+
+
+### Wildcard `Host`s No Longer Created
+
+In $productName$ 1.X, $productName$ would make sure that a wildcard `Host`, with a `hostname` of `"*"`, was always present.
+$productName$ 2.X does **not** force a wildcard `Host`: if you need the wildcard behavior, you will need to create
+a `Host` with a hostname of `"*"`.
+
+Of particular note is that $productName$ **will not** respond to queries to an IP address unless a wildcard
+`Host` is present. If `foo.example.com` resolves to `10.11.12.13`, and the only `Host` has a
+`hostname` of `foo.example.com`, then:
+
+- requests to `http://foo.example.com/` will work, but
+- requests to `http://10.11.12.13/` will **not** work.
+
+Adding a `Host` with a `hostname` of `"*"` will allow the second query to work.
+
+
+ Learn more about Host.
+
+
+### `Host` and `Mapping` Association
+
+The [`Host` CRD](../../topics/running/host-crd) continues to define information about hostnames, TLS certificates, and how to handle requests that are "secure" (using HTTPS) or "insecure" (using HTTP). The [`Mapping` CRD](../../topics/using/intro-mappings) continues to define how to map the URL space to upstream services.
+
+However, as of $productName$ 2.0, a `Mapping` will not be associated with a `Host` unless at least one of the following is true:
+
+- The `Mapping` specifies a `hostname` attribute that matches the `Host` in question.
+
+ - Note that a `getambassador.io/v2` `Mapping` has `host` and `host_regex`, rather than `hostname`.
+ - A `getambassador.io/v3alpha1` `Mapping` will honor `host` and `host_regex` as a transition aid, but `host` and `host_regex` are deprecated in favor of `hostname`.
+ - A `Mapping` that specifies `host_regex: true` will be associated with all `Host`s. This is generally far less desirable than using `hostname` with a DNS glob.
+
+- The `Host` specifies a `mappingSelector` that matches the `Mapping`'s Kubernetes `label`s.
+
+ - Note that a `getambassador.io/v2` `Host` has a `selector`, rather than a `mappingSelector`.
+ - A `getambassador.io/v3alpha1` `Host` ignores `selector` and, instead, looks only at `mappingSelector`.
+ - Where a `selector` got a default value if not specified, `mappingSelector` must be explicitly stated.
+
+Without either a `hostname` match or a `label` match, the `Mapping` will not be associated with the `Host` in question. This is intended to help manage memory consumption with large numbers of `Host`s and large numbers of `Mapping`s.
+
+
+ Learn more about Host.
+ Learn more about Mapping.
+
+
+### Independent `Host` Actions
+
+Each `Host` can specify its `requestPolicy.insecure.action` independently of any other `Host`, allowing for HTTP routing as flexible as HTTPS routing.
+
+
+ Learn more about Host.
+
+
+### `Host`, `TLSContext`, and TLS Termination
+
+As of $productName$ 2.0, **`Host`s are required for TLS termination**. It is no longer sufficient to create a [`TLSContext`](../../topics/running/tls/#tlscontext) by itself; the [`Host`](../../topics/running/host-crd) is required.
+
+The minimal setup for TLS termination is therefore a Kubernetes `Secret` of type `kubernetes.io/tls`, and a `Host` that uses it:
+
+```yaml
+---
+kind: Secret
+type: kubernetes.io/tls
+metadata:
+ name: minimal-secret
+data:
+ tls secret goes here
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: minimal-host
+spec:
+ hostname: minimal.example.com
+ tlsSecret:
+ name: minimal-secret
+```
+
+It is **not** necessary to explicitly state a `TLSContext` in the `Host`: setting `tlsSecret` is enough. Of course, `TLSContext` is still the ideal way to share TLS configuration between more than one `Host`. For further examples, see [Configuring $productName$ Communications](../../howtos/configure-communications).
+
+
+ Learn more about Host.
+ Learn more about TLSContext.
+
+
+### `Mapping`s, `TCPMapping`s, and TLS Origination
+
+A `getambassador.io/v2` `Mapping` or `TCPMapping` could specify `tls: true` to indicate TLS origination without supplying a certificate. This is not supported in `getambassador.io/v3alpha1`: instead, use an `https://` prefix on the `service`. In the [Mapping](../../topics/using/mappings/#using-tls), this is straightforward, but [there are more details for the `TCPMapping` when using TLS](../../topics/using/tcpmappings/#tcpmapping-and-tls).
+
+
+ Learn more about Mapping.
+
+
+### `Mapping`s and `labels`
+
+The `Mapping` CRD includes a `labels` field, used with rate limiting. The
+[syntax of the `labels`](../../topics/using/rate-limits#attaching-labels-to-requests) has changed
+for compatibility with Kubernetes 1.22.
+
+
+ Learn more about Mapping.
+
+
+### `Host`s and ACME
+
+In $productName$ 2.0, ACME will be disabled if a `Host` does not set `acmeProvider` at all (prior to $productName$ 2.0, not mentioning `acmeProvider` would result in the ACME client attempting, and failing, to start). If `acmeProvider` is set, but `acmeProvider.authority` is not set, the ACME client will continue to default to Let's Encrypt, in order to preserve compatibility with $productName$ prior to $productName$ 2.0. For further examples, see [Configuring $productName$ to Communicate](../../howtos/configure-communications).
+
+
+ Learn more about Host.
+
+
+## 3. Other Changes
+
+### Envoy V3 API by Default
+
+By default, $productName$ 2.X will configure Envoy using the
+[V3 Envoy API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/api).
+
+### More Performant Reconfiguration by Default
+
+In $productName$ 1.X, the environment variable `AMBASSADOR_FAST_RECONFIGURE` could be used to enable a higher performance implementation of the code $productName$ uses to validate and generate Envoy configuration. In $productName$ 2.X, this higher-performance mode is always enabled.
+
+### Changes to the `ambassador` `Module`, and the `tls` `Module`
+
+It is no longer possible to configure TLS using the `tls` element of the `ambassador` `Module` or using the `tls` `Module`. Both of these cases are correctly covered by the `TLSContext` resource.
+
+With the introduction of the `Listener` resource, a few settings have moved from the `Module` to the `Listener`.
+
+Configuration for the `PROXY` protocol is part of the `Listener` resource in $productName$ 2.X, so the `use_proxy_protocol` element of the `ambassador` `Module` is no longer supported. Note that the `Listener` resource can configure `PROXY` resource per-`Listener`, rather than having a single global setting. For further information, see the [`Listener` documentation](../../topics/running/listener).
+
+`xff_num_trusted_hops` has been removed from the `Module`, and its functionality has been moved to the `l7Depth` setting in the `Listener` resource.
+
+
+ Learn more about Listener.
+
+
+### `TLSContext` `redirect_cleartext_from` and `Host` `insecure.additionalPort`
+
+`redirect_cleartext_from` has been removed from the `TLSContext` resource; `insecure.additionalPort` has been removed from the `Host` CRD. Both of these cases are covered by adding additional `Listener`s. For further examples, see [Configuring $productName$ Communications](../../howtos/configure-communications).
+
+### Service Preview No Longer Supported
+
+Service Preview is no longer supported as of $productName$ 2.X, as its use cases are supported by Telepresence.
+
+### Edge Policy Console No Longer Supported
+
+The Edge Policy Console has been removed as of $productName$ 2.X, in favor of Ambassador Cloud.
+
+### `Project` CRD No Longer Supported
+
+The `Project` CRD has been removed as of $productName$ 2.X, in favor of Argo.
diff --git a/docs/edge-stack/3.9/about/changes-3.y.md b/docs/edge-stack/3.9/about/changes-3.y.md
new file mode 100644
index 000000000..fddc2b62e
--- /dev/null
+++ b/docs/edge-stack/3.9/about/changes-3.y.md
@@ -0,0 +1,56 @@
+import Alert from '@material-ui/lab/Alert';
+
+Major Changes in $productName$ 3.X
+==================================
+
+The 3.X family introduces a number of changes to ensure $productName$
+keeps up with latest Envoy versions and to support new features such as HTTP/3.
+We welcome feedback! Join us on [Slack](http://a8r.io/slack) and let us know what you think.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+## 1. Envoy Upgraded to 1.22
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy **1.22** which keeps $productName$ up-to-date with
+the latest security fixes, bug fixes, performance improvements and feature enhancements provided by Envoy Proxy. Most of the changes are under the hood but the most notable change to developers is the removal of support for Envoy V2 Transport Protocol. This means all external filters and LogServices must be updated to use the V3 Protocol.
+
+This also means some of the v2 runtime bootstrap flags have been removed as well:
+
+```yaml
+# No longer necessary because this was removed from Envoy
+# $productName$ already was converted to use the compressor API
+# https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+"envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+# Upgraded to v3, all support for V2 Transport Protocol removed
+"envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+"envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+# Developer will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+"envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+# V2 protocol removed so flag no longer necessary
+"envoy.reloadable_features.enable_deprecated_v2_api": true,
+```
+
+
+ Learn more about Envoy Proxy changes.
+
+
+## 2. Envoy V2 xDS Transport Protocol Support Removed
+
+With the upgrade to Envoy **1.22**, the V2 Envoy Transport Protocol is no longer supported and has been removed.
+$productName$ 3.X **only** supports [V3 Envoy API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/api).
+
+The `AuthService`, `RatelimitService`, `LogService` and `ExternalFilters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+
+## 3. Envoy V2 xDS Configration Support Removed
+
+Envoy can no longer be configured to use the v2 xDS configuration and will always use v3 xDS configuration. This change removes the AMBASSADOR_ENVOY_API_VERSION because it no longer configurable and will have no effect.
+
+
+## 4. Zipkin HTTP_JSON_V1 support is removed
+
+Envoy removed support for the older `HTTP_JSON_V1` collector_endpoint_version. If using the `zipkin` driver with the `TracingService`,
+then you will have to update it to use `HTTP_JSON` or `HTTP_PROTO`.
diff --git a/docs/edge-stack/3.9/about/faq.md b/docs/edge-stack/3.9/about/faq.md
new file mode 100644
index 000000000..14528f9a1
--- /dev/null
+++ b/docs/edge-stack/3.9/about/faq.md
@@ -0,0 +1,88 @@
+# Frequently Asked Questions
+
+## General
+
+### Why $productName$?
+
+Kubernetes shifts application architecture for microservices, as well as the
+development workflow for a full-cycle development. $productName$ is designed for
+the Kubernetes world with:
+
+* Sophisticated traffic management capabilities (thanks to its use of [Envoy Proxy](https://www.envoyproxy.io)), such as load balancing, circuit breakers, rate limits, and automatic retries.
+* API management capabilities such as a developer portal and OpenID Connect integration for Single Sign-On.
+* A declarative, self-service management model built on Kubernetes Custom Resource Definitions, enabling GitOps-style continuous delivery workflows.
+
+We've written about [the history of $productName$](https://blog.getambassador.io/building-ambassador-an-open-source-api-gateway-on-kubernetes-and-envoy-ed01ed520844), [Why $productName$ In Depth](../why-ambassador), [Features and Benefits](../features-and-benefits) and about the [evolution of API Gateways](../../topics/concepts/microservices-api-gateways/).
+
+### What's the difference between $OSSproductName$ and $AESproductName$?
+
+$OSSproductName$ is a CNCF Incubating project and provides the open-source core of $AESproductName$. Originally we called $OSSproductName$ the "Ambassador API Gateway", but as the project evolved, we realized that the functionality we were building had extended far beyond an API Gateway. In particular, the $AESproductName$ is intended to provide all the functionality you need at the edge -- hence, an "edge stack." This includes an API Gateway, ingress controller, load balancer, developer portal, and more.
+
+### How is $AESproductName$ licensed?
+
+The core $OSSproductName$ is open source under the Apache Software License 2.0. The GitHub repository for the core is [https://github.com/emissary-ingress/emissary](https://github.com/emissary-ingress/emissary). Some additional features of the $AESproductName$ (e.g., Single Sign-On) are not open source and available under a proprietary license.
+
+### Can I use the add-on features for $AESproductName$ for free?
+
+Yes! For more details please see the [$productName$ Licenses page](../../topics/using/licenses).
+
+### How does $productName$ use Envoy Proxy?
+
+$productName$ uses [Envoy Proxy](https://www.envoyproxy.io) as its core proxy. Envoy is an open-source, high-performance proxy originally written by Lyft. Envoy is now part of the Cloud Native Computing Foundation.
+
+### Is $productName$ production ready?
+
+Yes. Thousands of organizations, large and small, run $productName$ in production.
+Public users include Chick-Fil-A, ADP, Microsoft, NVidia, and AppDirect, among others.
+
+### What is the performance of $productName$?
+
+There are many dimensions to performance. We published a benchmark of [$productName$ performance on Kubernetes](/resources/envoyproxy-performance-on-k8s/). Our internal performance regressions cover many other scenarios; we expect to publish more data in the future.
+
+### What's the difference between a service mesh (such as Istio) and $productName$?
+
+Service meshes focus on routing internal traffic from service to service
+("east-west"). $productName$ focuses on traffic into your cluster ("north-south").
+While both a service mesh and $productName$ can route L7 traffic, the reality is that
+these use cases are quite different. Many users will integrate $productName$ with a
+service mesh. Production customers of $productName$ have integrated with Consul,
+Istio, and Linkerd2.
+
+## Common Configurations
+
+### How do I disable the 404 landing page?
+
+See the [Controlling the $productName$ 404 Page](../../howtos/controlling-404) how-to.
+
+### How do I disable the default Admin mappings?
+
+See the [Protecting the Diagnostics Interface](../../howtos/protecting-diag-access) how-to.
+
+## Troubleshooting
+
+### How do I get help for $productName$?
+
+We have an online [Slack community](http://a8r.io/slack) with thousands of
+users. We try to help out as often as possible, although we can't promise a
+particular response time. If you need a guaranteed SLA, we also have commercial
+contracts. [Contact sales](/contact-us/) for more information.
+
+### What do I do when I get the error `no healthy upstream`?
+
+This error means that $productName$ could not connect to your backend service.
+Start by verifying that your backend service is actually available and
+responding by sending an HTTP response directly to the pod. Then, verify that
+$productName$ is routing by deploying a test service and seeing if the mapping
+works. Then, verify that your load balancer is properly routing requests to
+$productName$. In general, verifying each network hop between your client and
+backend service is critical to finding the source of the problem.
+
+### What is the difference between the v3alpha1 and v1alpha1 CRDs?
+
+There are two different CRD versions supported by $productName$.
+The first are the `getambassador.io/v3alpha1` CRDs which were introduced with
+$productName$ 2.x. These are still supported and are not deprecated. As of $productName$ $version$, the new `gateway.getambassador.io/v1alpha1` CRDs have also been introduced.
+The `v1alpha1` CRDs have not only a new version, but also a new apigoup so that way they can
+be installed alongside the older CRDs without causing any conflicts.
+
+The `v1alpha1` CRDs are only available for the `Filter`, `FilterPolicy`, `WebApplicationFirewall`, and `WebApplicationFirewallPolicy` resources, and are the next generation of the CRDs that $productName$ will support. We are introducing them now to allow users to try them out without needing to stop using the `v3alpha1` CRDs. You can use `v1alpha1` and `v3alpha1` CRDs in the same cluster at the same time, but `FilterPolicies` are not able to reference `Filters` that do not match their CRD version.
diff --git a/docs/edge-stack/3.9/about/features-and-benefits.md b/docs/edge-stack/3.9/about/features-and-benefits.md
new file mode 100644
index 000000000..ecad16175
--- /dev/null
+++ b/docs/edge-stack/3.9/about/features-and-benefits.md
@@ -0,0 +1,39 @@
+# Features and benefits
+
+In cloud-native organizations, developers frequently take on responsibility for the full development lifecycle of a service, from development to QA to operations. $productName$ was specifically designed for these organizations where developers have operational responsibility for their service(s).
+
+As such, the $productName$ is designed to be used by both developers and operators.
+
+## Self-Service via Kubernetes Annotations
+
+$productName$ is built from the start to support _self-service_ deployments -- a developer working on a new service doesn't have to go to Operations to get their service added to the mesh, they can do it themselves in a matter of seconds. Likewise, a developer can remove their service from the mesh, or merge services, or separate services, as needed, at their convenience. All of these operations are performed via Kubernetes resources or annotations, so they can easily integrate with your existing development workflow.
+
+## Flexible canary deployments
+
+Canary deployments are an essential component of cloud-native development workflows. In a canary deployment, a small percentage of production traffic is routed to a new version of a service to test it under real-world conditions. $productName$ allows developers to easily control and manage the amount of traffic routed to a given service through annotations. [This tutorial](https://www.datawire.io/faster/canary-workflow/) covers a complete canary workflow using the $productName$.
+
+## Kubernetes-native architecture
+
+$productName$ relies entirely on Kubernetes for reliability, availability, and scalability. For example, $productName$ persists all state in Kubernetes, instead of requiring a separate database. Scaling the $productName$ is as simple as changing the replicas in your deployment, or using a [horizontal pod autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/).
+
+$productName$ uses [Envoy](https://www.envoyproxy.io) for all traffic routing and proxying. Envoy is a modern L7 proxy that is used in production at companies including Lyft, Apple, Google, and Stripe.
+
+## gRPC and HTTP/2 support
+
+$productName$ fully supports gRPC and HTTP/2 routing, thanks to Envoy's extensive capabilities in this area. See [gRPC and $productName$](../../howtos/grpc) for more information.
+
+## Istio Integration
+
+$productName$ integrates with the [Istio](https://istio.io) service mesh as the edge proxy. In this configuration, $productName$ routes external traffic to the internal Istio service mesh. See [Istio and $productName$](../../howtos/istio) for details.
+
+## Authentication
+
+$productName$ supports authenticating incoming requests with a custom authentication service, OAuth/OpenID Connect, or JWT. When configured, the $productName$ will check with a third party authentication service prior to routing an incoming request. For more information, see the [authentication guide](../../topics/using/filters/).
+
+## Rate limiting
+
+$productName$ supports rate limiting incoming requests. When configured, the $productName$ will check with a third party rate limit service prior to routing an incoming request. For more information, see the [rate limiting guide](../../topics/using/rate-limits/).
+
+## Integrated UI
+
+$productName$ includes a diagnostics service so that you can quickly debug issues associated with configuring the $productName$. For more information, see [running $productName$ in Production](../../topics/running).
diff --git a/docs/edge-stack/3.9/about/known-issues.md b/docs/edge-stack/3.9/about/known-issues.md
new file mode 100644
index 000000000..4a5c45f5b
--- /dev/null
+++ b/docs/edge-stack/3.9/about/known-issues.md
@@ -0,0 +1,20 @@
+import Alert from '@material-ui/lab/Alert';
+
+Known Issues in $productName$
+=============================
+
+## 2.2.1
+
+- TLS certificates using elliptic curves were incorrectly flagged as invalid. This issue is
+ corrected in $productName$ 2.2.2.
+
+## 2.2.0
+
+- If $productName$'s Pods start before Redis is responding, it may be necessary to restart
+ $productName$ for rate limiting to function correctly.
+
+- When using the ACME client provided with $productName$, a delayed ACME response can
+ prevent the `Host` using ACME from becoming active.
+
+ - Workaround: Make sure you have a wildcard `Host` that does not use ACME. The insecure routing
+ action doesn't matter: it's fine for this `Host` to redirect or even reject insecure requests.
diff --git a/docs/edge-stack/3.9/about/why-ambassador.md b/docs/edge-stack/3.9/about/why-ambassador.md
new file mode 100644
index 000000000..f16def3a1
--- /dev/null
+++ b/docs/edge-stack/3.9/about/why-ambassador.md
@@ -0,0 +1,54 @@
+# Why $productName$?
+
+$productName$ gives platform engineers a comprehensive, self-service edge stack for managing the boundary between end-users and Kubernetes. Built on the [Envoy Proxy](https://www.envoyproxy.io) and fully Kubernetes-native, $productName$ is made to support multiple, independent teams that need to rapidly publish, monitor, and update services for end-users. A true edge stack, $productName$ can also be used to handle the functions of an API Gateway, a Kubernetes ingress controller, and a layer 7 load balancer (for more, see [this blog post](https://blog.getambassador.io/kubernetes-ingress-nodeport-load-balancers-and-ingress-controllers-6e29f1c44f2d)).
+
+## How Does $productName$ work?
+
+$productName$ is a Kubernetes-native [microservices API gateway](../../topics/concepts/microservices-api-gateways) built on the open core of $OSSproductName$ and the [Envoy Proxy](https://www.envoyproxy.io). $productName$ is built from the ground up to support multiple, independent teams that need to rapidly publish, monitor, and update services for end-users. $productName$ can also be used to handle the functions of a Kubernetes ingress controller and load balancer (for more, see [this blog post](https://blog.getambassador.io/kubernetes-ingress-nodeport-load-balancers-and-ingress-controllers-6e29f1c44f2d)).
+
+## Cloud-native applications today
+
+Traditional cloud applications were built using a monolithic approach. These applications were designed, coded, and deployed as a single unit. Today's cloud-native applications, by contrast, consist of many individual (micro)services. This results in an architecture that is:
+
+* __Heterogeneous__: Services are implemented using multiple (polyglot) languages, they are designed using multiple architecture styles, and they communicate with each other over multiple protocols.
+* __Dynamic__: Services are frequently updated and released (often without coordination), which results in a constantly-changing application.
+* __Decentralized__: Services are managed by independent product-focused teams, with different development workflows and release cadences.
+
+### Heterogeneous services
+
+$productName$ is commonly used to route traffic to a wide variety of services. It supports:
+
+* configuration on a *per-service* basis, enabling fine-grained control of timeouts, rate limiting, authentication policies, and more.
+* a wide range of L7 protocols natively, including HTTP, HTTP/2, gRPC, gRPC-Web, and WebSockets.
+* Can route raw TCP for services that use protocols not directly supported by $productName$.
+
+### Dynamic services
+
+Service updates result in a constantly changing application. The dynamic nature of cloud-native applications introduces new challenges around configuration updates, release, and testing. $productName$:
+
+* Enables [progressive delivery](../../topics/concepts/progressive-delivery), with support for canary routing and traffic shadowing.
+* Exposes high-resolution observability metrics, providing insight into service behavior.
+* Uses a zero downtime configuration architecture, so configuration changes have no end-user impact.
+
+### Decentralized workflows
+
+Independent teams can create their own workflows for developing and releasing functionality that are optimized for their specific service(s). With $productName$, teams can:
+
+* Leverage a [declarative configuration model](../../topics/concepts/gitops-continuous-delivery), making it easy to understand the canonical configuration and implement GitOps-style best practices.
+* Independently configure different aspects of $productName$, eliminating the need to request configuration changes through a centralized operations team.
+
+## $productName$ is engineered for Kubernetes
+
+$productName$ takes full advantage of Kubernetes and Envoy Proxy.
+
+* All of the state required for $productName$ is stored directly in Kubernetes, eliminating the need for an additional database.
+* The $productName$ team has added extensive engineering efforts and integration testing to ensure optimal performance and scale of Envoy and Kubernetes.
+
+## For more information
+
+[Deploy $productName$ today](../../tutorials/getting-started) and join the community [Slack Channel](http://a8r.io/slack).
+
+Interested in learning more?
+
+* [Why did we start building $productName$?](https://blog.getambassador.io/building-ambassador-an-open-source-api-gateway-on-kubernetes-and-envoy-ed01ed520844)
+* [$productName$ Architecture overview](../../topics/concepts/architecture)
diff --git a/docs/edge-stack/3.9/aes-pages.yml b/docs/edge-stack/3.9/aes-pages.yml
new file mode 100644
index 000000000..bd03e5c39
--- /dev/null
+++ b/docs/edge-stack/3.9/aes-pages.yml
@@ -0,0 +1,24 @@
+# AES pages should be represented in a yaml array with the pathnames as the value
+# Ex:
+# - /docs
+# - /reference
+#
+
+/topics/using/filters/
+/topics/using/filters/oauth2
+/topics/using/filters/jwt
+/topics/using/filters/external
+/topics/using/filters/plugin
+/topics/using/edgectl/
+/topics/using/edgectl/edge-control
+/topics/using/edgectl/edge-control-in-ci
+/topics/using/edgectl/service-preview-install
+/topics/using/edgectl/service-preview-reference
+/topics/using/edgectl/service-preview-tutorial
+/topics/using/dev-portal
+/topics/using/edge-policy-console
+/topics/using/rate-limits/rate-limits/
+/topics/running/aes-redis
+/topics/running/aes-extensions/
+/topics/running/aes-extensions/authentication
+/topics/running/aes-extensions/ratelimit
diff --git a/docs/edge-stack/3.9/api-gateway-pages.yml b/docs/edge-stack/3.9/api-gateway-pages.yml
new file mode 100644
index 000000000..b9010fdbc
--- /dev/null
+++ b/docs/edge-stack/3.9/api-gateway-pages.yml
@@ -0,0 +1,72 @@
+# API Gateway pages should be represented in a yaml array with the pathnames as the value
+# Ex:
+# - /docs
+# - /reference
+#
+- /docs/dev-guide/canary-release-concepts
+- /docs/dev-guide/test-in-prod
+- /docs/guides/
+- /reference/add_request_headers
+- /reference/add_response_headers
+- /reference/ambassador-with-aws
+- /reference/canary
+- /reference/circuit-breakers
+- /reference/configuration
+- /reference/core/ambassador
+- /reference/core/crds
+- /reference/core/ingress-controller
+- /reference/core/load-balancer
+- /reference/core/resolvers
+- /reference/core/tls
+- /reference/cors
+- /reference/diagnostics
+- /reference/gzip
+- /reference/headers
+- /reference/host
+- /reference/host-crd
+- /reference/ambassadormappings
+- /reference/modules
+- /reference/prefix_regex
+- /reference/rate-limits
+- /reference/redirects
+- /reference/remove_request_headers
+- /reference/remove_response_headers
+- /reference/retries
+- /reference/rewrites
+- /reference/running
+- /reference/services/auth-service
+- /reference/services/log-service
+- /reference/services/rate-limit-service
+- /reference/services/services
+- /reference/services/tracing-service
+- /reference/shadowing
+- /reference/statistics
+- /reference/tcpmappings
+- /reference/timeouts
+- /reference/tls/cleartext-redirection
+- /reference/tls/client-cert-validation
+- /reference/tls/mtls
+- /reference/tls/origination
+- /user-guide/auth-tutorial
+- /user-guide/bare-metal
+- /user-guide/cd-declarative-gitops
+- /user-guide/cert-manager
+- /user-guide/consul
+- /user-guide/early-access
+- /user-guide/gitops-ambassador
+- /user-guide/grpc
+- /user-guide/helm
+- /user-guide/install-ambassador-oss
+- /user-guide/knative
+- /user-guide/linkerd2
+- /user-guide/monitoring
+- /user-guide/rate-limiting
+- /user-guide/rate-limiting-tutorial
+- /user-guide/security
+- /user-guide/sni
+- /user-guide/tls-termination
+- /user-guide/tracing-tutorial
+- /user-guide/tracing-tutorial-datadog
+- /user-guide/tracing-tutorial-zipkin
+- /user-guide/websockets-ambassador
+- /user-guide/with-istio
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md
new file mode 100644
index 000000000..2e99bc239
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md
@@ -0,0 +1,74 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **APIKey Filter** Type (v1alpha1)
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied. For more information about how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `APIKey Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 APIKey Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## APIKey Filter API Reference
+
+To create an APIKey Filter, the `spec.type` must be set to `apikey`, and the `apikey` field must contain the configuration for your
+APIKey Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-apikey-filter"
+ namespace: "example-namespace"
+spec:
+ type: "apikey" # required
+ apikey: APIKeyFilter # required when `type: "apikey"`
+ httpHeader: string # optional, default: `x-api-key`
+ keys: []APIKeyItem # required, min items: 1
+ - secretName: string # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### APIKeyFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `httpHeader` | `string` | The name of the http header where the api-key will be found (always case-insensitive). By default it will use the `x-api-key` header. |
+| `keys` | \[\][APIKeyItem][] | The set of APIKeys that are used to check the whether the incoming request is valid. |
+
+### APIKeyItem
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `secretName` | `string` | Defines how to resolve the values of the keys. Currently the only supported way to resolve a key is via a local secret. APIKeys cannot use shared secrets in a different namespace than the `APIKey Filter` resource. |
+
+**Note about Secret formatting**:
+When supplying secrets to an API Key filter, the keys of the Secret do not matter, but the value of your API Key must be [base64][] encoded.
+
+For example, if you want to create a secret for the API Key value `example-api-key-value`, the secret should look like:
+
+```yaml
+---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apikey-filter-keys
+ type: Opaque
+ data:
+ any-name-you-want: ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+```
+
+You can specify as many API Keys in the Secret as you like.
+
+[APIKeyItem]: #apikeyitem
+[FilterPolicy Resource]: ../filterpolicy
+[base64]: https://en.wikipedia.org/wiki/Base64
+[the v3alpha1 APIKey Filter api reference]: ../../../getambassador/v3alpha1/filter-apikey
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-external.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-external.md
new file mode 100644
index 000000000..42611ee18
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-external.md
@@ -0,0 +1,138 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **External Filter** Type (v1alpha1)
+
+The `External Filter` allows users to provide their own Kubernetes Service speaking the [ext_authz protocol][].
+$productName$ will send a request to this "External Service" that contains a copy of the incoming request. The External Service will then be able
+to examine details of the incoming request, make changes to its headers, and allow or reject it by sending back a response to $productName$.
+The external service is free to perform any logic it likes before responding to $productName$, allowing for custom filtering and
+processing on incoming requests. The `External Filter` may be used along with any of the other Filter types. For more information about
+how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `External Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `External Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 External Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## External Filter API Reference
+
+To create an External Filter, the `spec.type` must be set to `external`, and the `external` field must contain the configuration for your
+external filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-external-filter"
+ namespace: "example-namespace"
+spec:
+ type: "external" # required
+ external: ExternalFilter # required when `type: "external"`
+ protocol: Enum # required
+ authServiceURL: string # required, must be an absolute url
+ statusOnError: int # optional, default: `403`
+ failureModeAllow: bool # optional, default: `false`
+ timeout: Duration # optional, default: `"5s"`
+ httpSettings: HTTPSettings # optional, can only be set when `protocol: "http"`
+ pathPrefix: string # optional
+ allowedRequestHeaders: []string # optional
+ allowedAuthorizationHeaders: []string # optional
+ addLinkerdHeaders: bool # optional, default: `false`
+ grpcSettings: GRPCSettings # optional, can only be set when `protocol: "grpc"`
+ protocolVersion: Enum # optional, default: `"v3"`
+ include_body: IncludeBody # optional
+ maxBytes: int # required, default: `4096`
+ allowPartial: bool # required, default `true`
+ tlsConfig: TLSConfig # optional
+ certificate: TLSSource # required
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ caCertificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### ExternalFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `protocol` | `Enum` (`"http"`/`"grpc"`) | The type of protocol to use when communicating with the External Service. It is recommended to use "grpc" over "http" due to supporting additional capabilities. |
+| `authServiceURL` | `string` | The URL of the service performing the authorization / filtering logic. Must be an absolute URL. |
+| `statusOnError` | `int` | Allows overriding the status code returned when the External Service returns a non 200 response code for `protocol: "http"` or [DeniedHttpResponse][] for `protocol: "grpc"` |
+| `failureModeAllow` | `bool` | Determines what happens when $productName$ cannot communicate with the External Service due to network issues, or the service not being available. By default, the ExternalFilter will reject the request if it is unable to communicate. This can be overriden by setting this setting to `"true"` so that it fails open, allowing the request through to the upstream service. |
+| `timeout` | [Duration][] | The amount of time $productName$ will wait before erring on a timeout. **Note**: this value cannot be larger than the overall Auth Service timeout that is configured in Envoy or else it would effectively not have any timeout. |
+| `httpSettings` | [HTTPSettings][] | Settings specific to the http protocol. This can only be set when `protocol: "http"`. |
+| `grpcSettings` | [GRPCSettings][] | Settings specific to the grpc protocol. This can only be set when `protocol: "grpc"`. |
+| `include_body` | [IncludeBody][] | Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service. |
+| `tlsConfig` | [TLSConfig][] | Configures tls settings between $productName$ and the configured AuthService |
+
+### Duration
+
+**Appears On**: [ExternalFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### HTTPSettings
+
+**Appears On**:
+Settings specific to the http protocol. This can only be set when `protocol: "http"`.
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `pathPrefix` | `string` | Value that gets appended to the path of the downsteam request. Nothing is appended when this field is omitted |
+| `allowedRequestHeaders` | `[]string` | A list of headers from the downstream request that will be passed along as headers in the request to the external service. This includes metadata sent from Envoy to the EdgeStack Auth Service. By default, the following list of headers are passed through: `authorization`,`cookie`,`from`,`proxy-authorization`, `user-agent`, `x-forwarded-for`, `x-forwarded-host`, `x-forwarded-proto`. |
+| `allowedAuthorizationHeaders` | `[]string` | Headers from the External Service that will be added to the request to the upstream service. By default, the following headers are passed to the upstream service: `location`,`authorization`,`proxy-authenticate`,`set-cookie`,`www-authenticate`. Any additional headers that are needed should be added and are case-insenstive. |
+| `addLinkerdHeaders` | `bool` | When set to `true`, injects the `l5d-dst-override` header set to hostname and port of the external service which is used by [LinkerD][] when proxying through the Service Mesh. |
+
+### GRPCSettings
+
+**Appears On**: [ExternalFilter][]
+Settings specific to the http protocol. This can only be set when `protocol: "grpc"`.
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `protocolVersion` | `Enum` (`"v3"`) | Indicates the version of the transport protocol that the External Filter is using. This is only applicable to External Filters using `protocol: "grpc"`. Currently the only supported version is `"v3"`, so this field exists to provide compatability for future verions of ext_authz. |
+
+### IncludeBody
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `maxBytes` | `int` | Sets the number of bytes of the request body to buffer over to the External Service |
+| `allowPartial` | `bool` | Indicates whether the included body can be a partially buffered body or if the complete buffered body is expected. If not partial then a 413 http error is returned by Envoy. |
+
+### TLSConfig
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|-----------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `certificate.fromSecret` | SecretReference | Configures $productName$ to use the provided certificate to present to the server when connecting. Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret][] that contains the private key and public certificate that will be presented to the AuthService. Secret namespace defaults to Filter namespace if not set |
+| `caCertificate.fromSecret` | SecretReference | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret][] that contains the `tls.crt` key with the CA Certificate. Secret namespace defaults to Filter namespace if not set |
+
+[Duration]: #duration
+[HTTPSettings]: #httpsettings
+[ExternalFilter]: #externalfilter
+[GRPCSettings]: #grpcsettings
+[IncludeBody]: #includebody
+[TLSConfig]: #tlsconfig
+[the v3alpha1 External Filter api reference]: ../../../getambassador/v3alpha1/filter-external
+[ext_authz protocol]: ../../../../topics/running/services/ext-authz
+[FilterPolicy Resource]: ../filterpolicy
+[LinkerD]: https://linkerd.io/
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[DeniedHttpResponse]: https://github.com/envoyproxy/envoy/blob/1230c6cfba3791e4544b4ca23cacdbfc20a6fbaa/api/envoy/service/auth/v3/external_auth.proto
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md
new file mode 100644
index 000000000..34a4d7c07
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md
@@ -0,0 +1,174 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **JWT Filter** Type (v1alpha1)
+
+The `JWT Filter` performs JWT validation on a [bearer token][] present in the HTTP header. If the bearer token JWT doesn't validate,
+or has insufficient scope, an RFC 6750-complaint error response with a `www-authenticate` header is returned. The list of acceptable
+signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in the `Filter` configuration. Only RSA and `none`
+algorithms are supported.
+
+
+
+This doc is an overview of all the fields on the `JWT Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `JWT Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 JWT Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## JWT Filter API Reference
+
+To create a JWT Filter, the `spec.type` must be set to `jwt`, and the `jwt` field must contain the configuration for your
+JWT Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-jwt-filter"
+ namespace: "example-namespace"
+spec:
+ type: "jwt" # required
+ jwt: JWTFilter # required when `type: "jwt"`
+ jwksURI: string # optional, required unless `validAlgorithms: ["none"]`
+ validAlgorithms: []Enum # optional, default is all supported algos except for `"none"`
+ audience: string # optional (unless requireAudience: `true`)
+ requireAudience: bool # optional, default: `false`
+ issuer: string # optional (unless requireIssuer: `true`)
+ requireIssuer: bool # optional, default: `false`
+ requireExpiresAt: bool # optional, default: `false`
+ leewayForExpiresAt: Duration # optional
+ requireNotBefore: bool # optional, default: `false`
+ leewayForNotBefore: Duration # optoinal
+ requireIssuedAt: bool # optional, default: `false`
+ leewayForIssuedAt: Duration # optional
+ injectRequestHeaders: []AddHeaderTemplate # optional
+ - name: string # required
+ value: string (GoLang Template) # required
+ maxStale: Duration # optional
+ insecureTLS: bool # optional, default: `false`
+ renegotiateTLS: Enum # optional, default: `"never"`
+ errorResponse: CustomErrorResponse # optional
+ realm: string # optional, default is {name}.{namespace} of the JWT Filter
+ bodyTemplate: string (GoLang Template) # optional
+ headers: []AddHeaderTemplate # optional, max 16 items
+ - name: string # required
+ value: string (GoLang Template) # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### JWTFilter
+
+| **Field** | **Type** | **Description** |
+|------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+ `jwksURI` | `string` | A URI that returns the JWK Set per RFC 7517. This is required unless validAlgorithms=["none"], in that case verifying the signature of the token is disabled. This is considered unsafe and is discouraged when receiving tokens from untrusted sources. |
+ `validAlgorithms` | \[\][ValidAlgorithms][](`Enum`) | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+ `audience` | `string` | Identifies the recipient that the JWT is intended for and will be used to validate the provided token is intended for the configured audience. If not provided then `aud` claim on incoming token is not validated and will be considered valid. If `aud` is unset on the token by default it will be considered valid even if it doesn't match the audience value. To enforce that a token has the aud claim, then set `requireAudience: true`. |
+ `requireAudience` | `bool` | Modifies the validation behavior for when the audience claim (aud) is unset on the incoming token. `false` (default) => if aud claim is unset then claim is considered valid. `true` => if aud claim is unset then claim/token are invalid |
+ `issuer` | `string` | Identifies the expected AuthorizationServer that isssued the token. If not provided then the issuer claim will not be validated. If `issuer` is unset on the token by default it will be considered valid even if it doesn't match the expected issuer value. To enforce that a token has the issuer claim, then set `requireIssuer: true`. |
+ `requireIssuer` | `bool` | Modifies the validation behavior for when the issuer claim (iss) is unset on the incoming token. `false` (default) => if aud claim is unset on incoming token then claim is considered valid `true` => if exp claim is unset then claim is invalid |
+ `requireExpiresAt` | `bool` | Modifies the validation behavior for when the expiresAt claim (exp) is unset on the incoming token. `false` (default) => if exp claim is unset on incoming token then claim is valid `true` => if exp claim is unset then claim/token are invalid |
+ `leewayForExpiresAt` | [Duration][] | Allows token expired by this much to still be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `requireNotBefore` | `bool` | Modifies the validation behavior for when the not before time claim (nbf) is unset on the incoming token. `false` (default) => if `nbf` claim is unset on incoming token then claim is valid `true` => if `nbf` claim is unset then claim/token are invalid |
+ `leewayForNotBefore` | [Duration][] | Allows tokens that shouldn't be used until this much in the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `requireIssuedAt` | `bool` | Modifies the validation behavior for when the issuedAt claim (iat) is unset on the incoming token. `false` (default) => if `iat` claim is unset on incoming token then claim is valid `true` => if `iat` claim is unset then claim/token are invalid |
+ `leewayForIssuedAt` | [Duration][] | Allows tokens issued by this much into the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `injectRequestHeaders` | \[\][AddHeaderTemplate][] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream request headers as values. For example, attaching user email claim to a header from the token. |
+ `maxStale` | [Duration][] | Sets the duration that JWKs keys and OIDC discovery responses will be cached, ignoring any caching headers when configured |
+ `insecureTLS` | `bool` | Disables TLS verification for cases when jwksURI begins with `https://`. |
+ `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the JWTFilter will renegotiateTLS with the `jwksURI` server and if so what supported method of renegotiation will be used. |
+ `errorResponse` | [CustomErrorResponse][] | Allows setting a custom Response to the downstream client when an invalid JWT is received. |
+
+### Duration
+
+**Appears On**: [JWTFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ValidAlgorithms
+
+**Appears On**: [JWTFilter][]
+The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not
+in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP,
+as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided
+to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected.
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+
+### AddHeaderTemplate
+
+**Appears On**: [JWTFilter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream
+request headers as values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+`value` is the value of the header to set and is evaluated as a special GoLang Template.
+This allows the header value to be set based on the JWT value. The value is specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.token.Raw` → The raw JWT (`string`)
+- `.token.Header` → The JWT header, as parsed JSON (`map[string]interface{}`)
+- `.token.Claims` → The JWT claims, as parsed JSON (`map[string]interface{}`)
+- `.token.Signature` → The token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+Also available to the template are [the standard functions available to Go text/templates][], as well as:
+
+- a `hasKey` function that takes the a string-indexed map as arg1, and returns whether it contains the key arg2. (This is the same as [the Sprig function of the same name][].)
+- a `doNotSet` function that causes the result of the template to be discarded, and the header field to not be adjusted. This is useful for only conditionally setting a header field; rather than setting it to an empty string or `""`. Note that this does not unset an existing header field of the same name and could be a potential security vulnerability depending on how this is used if an untrusted client spoofs these headers.
+
+
+ Any headers listed will override (not append to) the original request header with that name.
+
+
+### CustomErrorResponse
+
+**Appears On**: [JWTFilter][]
+Allows setting a custom Response to the downstream client when an invalid JWT is received.
+
+| **Field** | **Type** | **Description** |
+|------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `realm` | `string` | Indicates the scope of protection or the application that is checking the token. By default, this is set to the fully qualified name of the `JWT Filter` as `"{name}.{namespace}"` to identify which filter rejected the error. This can be overriden to provide more relevant information to end-users. |
+| `bodyTemplate` | `string` (GoLang Template) | Golang `text/template` string that will be evaluated and used to build the format returned. |
+| `headers` | \[\][AddHeaderTemplate][] | Allows providing additional http response headers for the error response. The current maximum is 16 headers, which aligns with the Gateway-API and modified headers on HTTPRoutes. |
+
+`bodyTemplate` specifies body of the error response returned to the downstream client; specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.status_code` → The HTTP status code to be returned (`int`)
+- `.httpStatus` → An alias for .status_code (`int`, hidden from `{{ . | json "" }}`)
+- `.message` → The error message (`string`)
+- `.error` → The raw Go error object that generated .message (`error`, hidden from `{{ . | json "" }}`)
+- `.error.ValidationError` → The JWT validation error, will be nil if the error is not purely JWT validation (`jwt.ValidationError` insufficient scope, malformed or missing Authorization header)
+- `.request_id` → The Envoy request ID, for correlation (`string`, hidden from `{{ . | json "" }}` unless `.status_code` is in the `5xx` range)
+- `.requestId` → An alias for .request_id (`string`, hidden from `{{ . | json "" }}`)
+
+Also availabe to the template are [the standard functions available to Go text/templates][], as well as:
+
+- A `json` function that formats arg2 as JSON, using the arg1 string as the starting indentation. For example, the template `{{ json "indent>" "value" }}` would yield the string `indent>"value"`.
+
+[JWTFilter]: #jwtfilter
+[ValidAlgorithms]: #validalgorithms
+[AddHeaderTemplate]: #addheadertemplate
+[CustomErrorResponse]: #customerrorresponse
+[Duration]: #duration
+[the v3alpha1 JWT Filter api reference]: ../../../getambassador/v3alpha1/filter-jwt
+[bearer token]: https://datatracker.ietf.org/doc/html/rfc6750
+[Go text/template string]: https://pkg.go.dev/text/template
+[the standard functions available to Go text/templates]: https://pkg.go.dev/text/template#hdr-Functions
+[the Sprig function of the same name]: https://masterminds.github.io/sprig/dicts.html#haskey
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md
new file mode 100644
index 000000000..26ba7d496
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md
@@ -0,0 +1,341 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **OAuth2 Filter** Type (v1alpha1)
+
+The OAuth2 Filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+
+
+
+This doc is an overview of all the fields on the `OAuth2 Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `OAuth2 Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 OAuth2 Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## OAuth2 Filter API Reference
+
+To create an OAuth2 Filter, the `spec.type` must be set to `oauth2`, and the `oauth2` field must contain the configuration for your
+OAuth2 filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-oauth2-filter"
+ namespace: "example-namespace"
+spec:
+ type: "oauth2" # required
+ oauth2: OAuth2Filter # required when `type: "oauth2"`
+ authorizationURL: string # required, must be an absolute url
+ expirationSafetyMargin: Duration # optional
+ injectRequestHeaders: []AddHeaderTemplate # optional
+ - name: string # required
+ value: string (GoLang Template) # required
+ allowMalformedAccessToken: bool # optional, default: `false`
+ accessTokenValidation: Enum # optional, default: `"auto"`
+ accessTokenJWTFilter: JWTFilterReference # optional
+ name: string # required
+ namespace: string # optional
+ inheritScopeArgument: bool # optional, default: `false`
+ stripInheritedScope: bool # optional, default: `false`
+ arguments: JWTArguments # optional
+ scope: []string # optional
+ clientAuthentication: ClientAuthentication # optional
+ method: Enum # optional, default: `"HeaderPassword"`
+ jwtAssertion: JWTAssertion # optional
+ setClientID: bool # optional, default: `false`
+ audience: string # optional
+ signingMethod: Enum # optional, default: `RS256`
+ lifetime: Duration # optional, default: `"1m`
+ setNBF: bool # optional, default: `false`
+ nbfSafetyMargin: Duration # optional
+ setIAT: bool # optional, default: `false`
+ otherClaims: []byte # optional, default: `{}`
+ otherHeaderParameters: []byte # optional, default: `{}`
+ grantType: Enum # required
+ authorizationCodeSettings: AuthorizationCodeSettings # optional, used when `grantType: "AuthorizationCode"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+ name: string # optional
+ namespace: string # optional
+ maxStale: Duration # optional
+ insecureTLS: bool # optional, default: `false`
+ renegotiateTLS: Enum # optional, default: `"never"`
+ protectedOrigins: []Origin # required, min items: 1, max items: 16
+ - origin: string # required, must be an absolute URL, max length: 255
+ includeSubdomains: bool # optional, defualt: `false`
+ allowedInternalOrigins: []string # optional, max items: 16
+ resourceOwnerSettings: ResourceOwnerSettings # optoinal, used when `grantType: "ResourceOwnder"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+ passwordSettings: PasswordSettings # optional, used when `grantType: "Password"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### OAuth2Filter
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `authorizationURL` | `string` | Identity Provider Issuer URL which hosts the OpenID provider well-known configurartion. The URL must be an absolute URL. Per [OpenID Connect Discovery 1.0][] the configuration must be provided in a json document at the path `/.well-known/openid-configuration`. This is used by the OAuth2 Filter for determining things like the AuthorizationEndpoint, TokenEndpoint, JWKs endpint, etc... |
+| `expirationSafetyMargin` | [Duration][] | Sets a buffer to check if the Token is expired or is going to expire within the safety margin. This is to ensure the application has enough time to reauthenticate to adjust for clock skew and network latency. By default, no safety margin is added. If a token is received with an expiration less than this field, then the token is considered to already be expired. |
+| `injectRequestHeaders` | \[\][][AddHeaderTemplate] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token. |
+| `allowMalformedAccessToken` | `bool` | Allows any access token even if they are not RFC 6750-compliant. |
+| `accessTokenValidation` | `Enum`(`"auto"`,`"jwt"`,`"userinfo"`) | Sets the method used for validating an AccessToken. |
+| `accessTokenJWTFilter` | [JWTFilterReference][] | Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes |
+| `clientAuthentication` | [ClientAuthentication][] | Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password. |
+| `grantType` | `Enum`(`"AuthorizationCode"`,`"ClientCredentials"`,`"Password"`,`"ResourceOwner"`) | Sets the Authorization Flow that the filter will use to authenticate the incoming request. |
+| `authorizationCodeSettings` | [AuthorizationCodeSettings][] | Specific settings that configure the `AuthorizationCode` grant type. |
+| `resourceOwnerSettings` | [ResourceOwnerSettings][] | Specific settings that configure the `ResourceOwner` grant type. |
+| `passwordSettings` | [PasswordSettings][] | Specific settings that configure the `Password` grant type. |
+
+**`grantType` options**:
+
+- `"AuthorizationCode"`: Authenticate by redirecting to a login page served by the identity provider.
+- `"Password"`: Authenticate by requiring `X-Ambassador-Username` and `X-Ambassador-Password` on all incoming requests, and use them to authenticate with the identity provider using the OAuth2 Resource Owner Password Credentials grant type.
+- `"ClientCredentials"`: Authenticate by requiring that the incoming HTTP request include as headers the credentials for Ambassador to use to authenticate to the identity provider.
+ - The type of credentials needing to be submitted depends on the `clientAuthentication.method` (below):
+ - For `"HeaderPassword"` and `"BodyPassword"`, the headers `X-Ambassador-Client-ID` and `X-Ambassador-Client-Secret` must be set.
+ - For `"JWTAssertion"`, the `X-Ambassador-Client-Assertion` header must be set to a JWT that is signed by your client secret, and conforms with the requirements in RFC 7521 section 5.2 and RFC 7523 section 3, as well as any additional specified by your identity provider.
+
+**`accessTokenValidation` options**:
+
+- `"jwt"`: Validates the Access Token as a JWT.
+
+ - By default: It accepts the RS256, RS384, or RS512 signature algorithms, and validates the signature against the JWKS from
+OIDC Discovery. It then validates the `exp`, `iat`, `nbf`, `iss` (with the Issuer from OIDC Discovery), and `scope` claims: if present,
+none of the scope values are required to be present. This relies on the identity provider using non-encrypted signed JWTs as
+Access Tokens, and configuring the signing appropriately
+ - This behavior can be modified by delegating to [JWT Filter][] with `accessTokenJWTFilter`:
+
+- `"userinfo"`: Validates the access token by polling the OIDC UserInfo Endpoint. This means that $productName$ must initiate
+an HTTP request to the identity provider for each authorized request to a protected resource. This performs poorly,
+but functions properly with a wider range of identity providers. It is not valid to set `accessTokenJWTFilter` if
+`accessTokenValidation`: `userinfo`.
+
+- `"auto"` attempts to do `"jwt"` validation if any of these conditions are true:
+ - `accessTokenJWTFilter` is set
+ - `grantType` is `"ClientCredentials"`
+ - the Access Token parses as a JWT and the signature is valid,
+ - If none of the above conditions are satisfied, it falls back to `"userinfo"` validation.
+
+### Duration
+
+**Appears on**: [Oauth2Filter][], [JWTAssertion][], [AuthorizationCodeSettings][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### AddHeaderTemplate
+
+**Appears On**: [OAuth2Filter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+The header value can be set based on the JWT value. If an `OAuth2 Filter` is chained with a [JWT filter][] with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the `OAuth2 Filter` will populate the value. The value is specified as a [Go text/template][] string, with the following data made available to it:
+
+- `.token.Raw` → The access token raw JWT (`string`)
+- `.token.Header` → The access token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.token.Claims` → The access token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.token.Signature` → The access token signature (`string`)
+- `.idToken.Raw` → The raw id token JWT (`string`)
+- `.idToken.Header` → The id token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Claims` → The id token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Signature` → The id token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+### JWTFilterReference
+
+**Appears On**: [OAuth2Filter][]
+Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes
+
+| **Field** | **Type** | **Description** |
+|------------------------|-------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `namespace` | `string` | Namespace of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `inheritScopeArgument`:| `bool` | Will use the same scope as set on the FilterPolicy OAuth2Arguments. If the JWTFilter sets a scope as well then the union of the two will be used. |
+| `stripInheritedScope` | `bool` | Determines whether or not to santized a scope that is formatted as an URI and was inherited from the FilterPolicy OAuth2Arguments. This will be done prior to passing it along to the referenced JWTFilter. This requires that InheritScopeArgument is true. |
+| `arguments` | [JWTArguments][] | Defines the input arguments that can be set for a JWTFilter. |
+
+### JWTArguments
+
+**Appears On**: [JWTFilterReference][]
+Defines the input arguments that can be set for a JWTFilter.
+
+| **Field** | **Type** | **Description** |
+|------------|-------------|---------------------------------------------------------------------------------------------------------|
+| `scope` | `[]string` | A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope. |
+
+**Some notes about `scope`**:
+
+- If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed.
+- If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+- As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+- The ordering of scope values does not matter, and is ignored.
+
+
+### ClientAuthentication
+
+**Appears On**: [OAuth2Filter][]
+Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password.
+
+| **Field** | **Type** | **Description** |
+|----------------|----------------------------------|---------------------------------------------------------------------------------------------------------|
+| `method` | `Enum`(`"HeaderPassword"`,`"BodyPassword"`,`"JWTAssertion"`) | Defines the type of client authentication that will be used |
+| `jwtAssertion` | [JWTAssertion][] | This field is only used when `method: "JWTAssertion"`. Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow. |
+
+`method` options:
+
+- `"HeaderPassword"`: Treat the client secret as a password, and pack that in to an HTTP header for HTTP Basic authentication.
+- `"BodyPassword"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+- `"JWTAssertion"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+
+### JWTAssertion
+
+**Appears On**: [ClientAuthentication][]
+Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|-------------------------|---------------------------------------------------------------------------------------------------------|
+| `setClientID` | `bool` | Whether to set the Client ID as an HTTP parameter; setting it as an HTTP parameter is optional (per RFC 7521 §4.2) because the Client ID is also contained in the JWT itself, but some identity providers document that they require it to also be set as an HTTP parameter anyway. |
+| `audience` | `string` | This field is ignored when `grantType: "ClientCredentials"`. The audience your IDP requires for authentication. If not set then the default will be to use the token endpoint from the OIDC discovery document. |
+| `signingMethod` | [ValidAlgorithms][] | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `lifetime` | [Duration][] | This field is ignored when `grantType: "ClientCredentials"`. The lifetime of the generated JWT; just enough time for the request to the identity provider to complete (plus possibly an extra allowance for clock skew). |
+| `setNBF` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "nbf" ("Not Before") claim in the generated JWT. |
+| `nbfSafetyMargin` | [Duration][] | This field is only used when `setNBF: true` The safety margin to build-in to the "nbf" claim, to allow for clock skew between ambassador and the identity provider. |
+| `setIAT` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "iat" ("Issued At") claim in the generated JWT. |
+| `otherClaims` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Key/value pairs that will be add to the JWT sent for client Auth to the Identity Provider |
+| `otherHeaderParameters` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Any extra JWT header parameters to include in the generated JWT non-standard claims to include in the generated JWT; only the "typ" and "alg" header parameters are set by default. |
+
+### ValidAlgorithms
+
+**Appears On**: [JWTAssertion][]
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+ - The secret must be a PEM-encoded Eliptic Curve private key
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+ - The secret is a raw string of bytes; it can contain anything
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+ - The secret must be a PEM-encoded RSA private key
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+ - The secret must be a PEM-encoded RSA private key
+
+### AuthorizationCodeSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `AuthorizationCode` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. The Kubernetes Secret must of the `generic` type, with the value stored under the key `oauth2-client-secret` |
+| `maxStale` | [Duration][] | How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also ignores the `no-store` and `no-cache` Cache-Control directives on responses. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Note that if you MUST set `maxStale` as a consistent value on each `Filter` resource to get predictable caching behavior. |
+| `insecureTLS` | `bool` | Tells the $productName$ to skip verifying the IdentityProvider server when communicating with the various endpoints. This is typically needed when using an IdentityProvider configured with self-signed certs. |
+| `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the OAuth2 Filter will renegotiateTLS with the iDP server and if so what supported method of renegotiation will be used. |
+| `protectedOrigins` | \[\][Origin][] | This field is only used when `grantType: "AuthorizationCode"`. List of origins (domains) that the OAuth2 Filter is configured to protect. Setting multiple origins allows for protecting multiple domains using the same Session and Token that is retrieved from the Identity Provider. When setting multiple protected origins, the first origin will be used for the final redirect to the IdentityProvider therefore the identity provider needs to be configured to allow redirects from that origin. However, it is recommended that all protected origins are registered with the IdentityProvider because this is subject to change in the future. Only the scheme `(https://)` and authority `(example.com:1234)` parts are used; the path part of the URL is ignored. You will need to register each origin in `protectedOrigins` as an authorized callback endpoint with your identity provider. The URL will look like `{{ORIGIN}}/.ambassador/oauth2/redirection-endpoint`. |
+
+> **Note**: If you provide more than one protectedOrigin, all share the same authentication system, so that logging into one origin logs you into all origins; to have multiple domains that have separate logins, use separate `Filters`.
+
+### SecretReference
+
+**Appears On**: [AuthorizationCodeSettings][], [PasswordSettings][], [ResourceOwnerSettings][]
+A reference to a [Kubernetes Secret][].
+
+| **Field** | **Type** | **Description** |
+|-------------|------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the Kubernetes Secret being referenced. |
+| `namespace` | `string` | Namespace of the Kubernetes Secret being referenced. |
+
+### Origin
+
+**Appears On**: [AuthorizationCodeSettings][]
+A domain that the OAuth2 Filter is configured to protect. It is recommended that all protected origins are registered with the IdentityProvider because this is subject to change in the future.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|------------|---------------------------------------------------------------------------------------------------------|
+| `origin` | `string` | The absolute URL (schema://hostname) that is protected by the OAuth2 Filter |
+| `includeSubdomains` | `bool` | Enables protecting sub-domains of the domain identified in the Origin field. Example, when `Origin=https://example.com` then the subdomain of `https://app.example.com` would be watched. |
+| `allowedInternalOrigins` | `[]string` | Indentifies a list of allowed internal origins that were set by a downstream proxy via a host header rewrite. The origins identified in this list ensures the request is allowed and will ensure it redirects correctly to the upstream origin. For example, a downstream client will communicate with an origin of `https://example.com` but then an internal proxy will do a rewrite so that the host header received by Edge Stack is `http://example.internal`. |
+
+**Note about `allowedInternalOrigins`**: This field is primarily used to allow you to tell $productName$ that there is another gateway
+in front of $productName$ that rewrites the Host header, so that on the internal network between that gateway and $productName$, the
+origin appears to be `allowedInternalOrigins` instead of `origin`. As a special-case the scheme and/or authority of the `allowedInternalOrigins`
+may be `"*"`, which matches any scheme or any domain respectively.
+Using `"*"` is most useful in configurations with exactly one protected origin; in such a configuration, $productName$ doesn't need
+to know what the origin looks like on the internal network, just that a gateway in front of $productName$ is rewriting it.
+It is invalid to use `"*"` with `includeSubdomains: true`.
+
+For example, if you have a gateway in front of $productName$ handling traffic for `myservice.example.com`, terminating TLS and routing
+that traffic to Ambassador with the name `ambassador.internal`, you might write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - http://ambassador.internal
+```
+
+or, to avoid being fragile to renaming ambassador.internal to something else, since there are not multiple origins that the `Filter` must
+distinguish between, you could instead write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - "*://*"
+```
+
+### ResourceOwnerSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `ResourceOwner` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. |
+
+### PasswordSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `Password` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. |
+
+[AddHeaderTemplate]: #addheadertemplate
+[Oauth2Filter]: #oauth2filter
+[JWTFilterReference]: #jwtfilterreference
+[ClientAuthentication]: #jwtfilterreference
+[AuthorizationCodeSettings]: #authorizationcodesettings
+[ResourceOwnerSettings]: #resourceownersettings
+[PasswordSettings]: #passwordsettings
+[JWTArguments]: #jwtarguments
+[JWTAssertion]: #jwtassertion
+[ValidAlgorithms]: #validalgorithms
+[SecretReference]: #secretreference
+[Origin]: #origin
+[Duration]: #duration
+[JWT Filter]: ../filter-jwt
+[the v3alpha1 OAuth2 Filter api reference]: ../../../getambassador/v3alpha1/filter-oauth2
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
+[Kubernetes secret]: https://kubernetes.io/docs/concepts/configuration/secret/
+[OpenID Connect Discovery 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md
new file mode 100644
index 000000000..7dddf64ff
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md
@@ -0,0 +1,42 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Plugin Filter** Type (v1alpha1)
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled into a .so file,
+which you load into the Envoy Proxy container at `/etc/ambassador-plugins/${NAME}.so`. For more information about how requests are
+matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `Plugin Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 Plugin Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## Plugin Filter API Reference
+
+To create a Plugin Filter, the `spec.type` must be set to `plugin`, and the `plugin` field must contain the configuration for your Plugin Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-plugin-filter"
+ namespace: "example-namespace"
+spec:
+ type: "plugin" # required
+ plugin: PluginFilter # required when `type: "plugin"`
+ name: string # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+`name`: Indicates the compiled binaries name excluding the extension for the Plugin.
+Envoy Proxy will look for the .so file in the `/etc/ambassador-plugins` directory.
+For example, if `name: "example-plugin"` the .so file should be available at
+`"/etc/ambassador-plugins/example-plugin.so"` on the Envoy Proxy container.
+
+[FilterPolicy Resource]: ../filterpolicy
+[the v3alpha1 Plugin Filter api reference]: ../../../getambassador/v3alpha1/filter-plugin
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter.md
new file mode 100644
index 000000000..91ff36574
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filter.md
@@ -0,0 +1,78 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **Filter** Resource (v1alpha1)
+
+The `Filter` custom resource works in conjunction with the [FilterPolicy custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending them to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests, such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute against those requests. Filters are largely used to add built-in authentication and security, but
+$productName$ also supports developing custom filters to add your own processing and logic.
+
+
+
+This doc is an overview of all the fields on the `Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## Filter API Reference
+
+Filtering is configured using `Filter` custom resources. The body of the resource `spec` depends on the filter type:
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-filter"
+ namespace: "example-namespace"
+spec:
+ type: Enum # required
+ jwt: JWTFilter # optional, required when `type: "jwt"`
+ oauth2: OAuth2Filter # optional, required when `type: "oauth2"`
+ apikey: APIKeyFilter # optional, required when `type: "apikey"`
+ external: ExternalFilter # optional, required when `type: "external"`
+ plugin: PluginFilter # optional, required when `type: "plugin"`
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### FilterSpec
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum` (`"jwt"`/`"oauth2"`/`"apikey"`/`"external"`/`"plugin"`) | Required field that identifies the type of the Filter that is configured to be executed on a request. |
+| `jwt` | [JWTFilter][] | Provides configuration for the JWT Filter type |
+| `oauth2` | [OAuth2Filter][] | Provides configuration for the OAuth2 Filter type |
+| `apikey` | [APIKeyFilter][] | Provides configuration for the APIKey Filter type |
+| `external` | [ExternalFilter][] | Provides configuration for the External Filter type |
+| `plugin` | [PluginFilter][] | Provides configuration for the Plugin Filter type |
+
+### FilterStatus
+
+This field is set automatically by $productName$ to provide info about the status of the `Filter`.
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-------------------------------------------------------------------------------------------------------------------|
+| `conditions` | \[\][metav1.Condition][] | Describes the current conditions of the WebApplicationFirewall, known conditions are `Accepted`;`Ready`;`Rejected` |
+
+
+ The short name for Filter is fil, so you can get filters using kubectl get filter or kubectl get fil.
+
+
+[FilterPolicy custom resource]: ../filterpolicy
+[JWTFilter]: ../filter-jwt
+[PluginFilter]: ../filter-plugin
+[OAuth2Filter]: ../filter-oauth2
+[APIKeyFilter]: ../filter-apikey
+[ExternalFilter]: ../filter-external
+[the v3alpha1 Filter api reference]: ../../../getambassador/v3alpha1/filter
+[metav1.Condition]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md
new file mode 100644
index 000000000..07caacdd4
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md
@@ -0,0 +1,183 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **FilterPolicy** Resource (v1alpha1)
+
+The `FilterPolicy` custom resource works in conjunction with the [Filter custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute on those requests.
+
+
+
+This doc is an overview of all the fields on the `FilterPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `FilterPolicy` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 FilterPolicy api reference][].
+
+
+ v1alpha1FilterPolicies can only be reference v1alpha1Filters.
+
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## FilterPolicy API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec: FilterPolicy
+ rules: []FilterPolicyRule # required, min items: 1
+ - host: string # optional, default: `"*"`
+ path: string # optional, default: `"*"`
+ precedence: int # optional
+ filterRefs: []FilterReference # optional, max items: 5
+ - name: string # required
+ namespace: string # optional
+ onDeny: Enum # optional, default: `"break"`
+ onAllow: Enum # optional, default: `"continue"`
+ ifRequestHeader: HTTPHeaderMatch # optional
+ type: Enum # optional, default: `"Exact"`
+ name: string # required
+ value: string # optional, max length: 4096
+ negate: bool # optional, default: `false`
+ arguments: FilterArguments # optional
+ type: Enum # required
+ jwt: JWTArguments # optional, required when `type: "jwt"`
+ scope: []string # optional
+ oauth2: OAuth2Arguments # optional, required when `type: "oauth2"`
+ scope: []string # optional
+ insteadOfRedirect: OAuth2Redirect # optional
+ statusCode: int # optional
+ ifRequestHeader: HTTPHeaderMatch # optional
+ filterRefs: []FilterReference # optional
+ sameSite: Enum # optional
+status: FilterPolicyStatus # field managed by controller
+ conditions: []metav1.Condition # max items: 8
+ rules: []FilterPolicyRuleStatus # max items: 64
+ - index: string
+ host: string
+ path: string
+ conditions: []metav1.Condition
+```
+
+### FilterPolicy
+
+| **Field** | **Type** | **Description** |
+|------------|----------------------------|-----------------------------------------------------------------------------------|
+| `rules` | \[\][FilterPolicyRule][] | Set of matching rules that are checked against incoming request to determine which set of Filter's to apply. If no matches are found then the request is allowed through to the upstream service without executing any Filters. |
+
+### FilterPolicyRule
+
+**Appears on**: [FilterPolicy][]
+Configures matching rules that are checked against incoming request to determine which `Filter` to apply (if any).
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-----------------------------------------------------------------------------------|
+| `host` | `string` | "glob-string" that matches on the `:authority` header of the incoming request. If not set it will match on all incoming requests. |
+| `path` | `string` | "glob-string" that matches on the request path. If not provided then it will match on all incoming requests. |
+| `filterRefs` | \[\][FilterReference][] | List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths. |
+| `precedence` | `int` | Allows forcing a precedence ordering on the rules. By default the rules are evaluated in the order they are in the `FilterPolicy.spec.rules` field. However, multiple FilterPolicy's can be applied to a cluster. To ensure that a specific ordering is enforced then using a precedence is an option. |
+
+### FilterReference
+
+**Appears on**: [FilterPolicyRule][], [OAuth2Redirect][]
+List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths.
+
+| **Field** | **Type** | **Description** |
+|-------------------|-----------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name that identifies the Filter |
+| `namespace` | `string` | Kubernetes namespace that the Filter resides. It must be a RFC 1123 label. Valid values include: `"example"`, Invalid values include: `"example.com"` (`.` is an invalid character). This validation is based off of the [corresponding Kubernetes validation]. |
+| `onDeny` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `onAllow` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not. |
+| `arguments` | [FilterArguments][] | Strongly typed input arguments that can be passed into a Filter on per [FilterReference][] level allowing for different behavior on different Rules. |
+
+### HTTPHeaderMatch
+
+**Appears on**: [FilterPolicyRule][], [OAuth2Redirect][]
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum`(`"Exact"`,`"RegularExpression"`) | The semantics of how HTTP header values should be evaluated |
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of HTTP Header to be matched. If type is RegularExpression then this must be a valid regex with length being at least 1. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### FilterArguments
+
+**Appears on**: [FilterPolicyRule][]
+Strongly typed input arguments that can be passed into a Filter on per [FilterReference][] level allowing for different behavior on different Rules.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum` (`"jwt"`/`"oauth2"`) | Identifies the expected type of the arguments that will be passed to the `FilterRef`. This must match the type of the `filterRef` and if it doesn't the `FilterPolicy` `rule` will be considered invalid and a status condition will be updated to indicate the mismatch. |
+| `jwt` | [JWTArguments][] | Defines the input arguments that can be set for an [JWT Filter][] on a per `FilterPolicy` `rule` level. |
+| `oauth2` | [OAuth2Arguments][] | Defines the input arguments that can be set for an [OAuth2 Filter][] on a per `FilterPolicy` `rule` level. |
+
+### JWTArguments
+
+**Appears on**: [FilterArguments][]
+Defines the input arguments that can be set for an [JWT Filter][] on a per `FilterPolicy` `rule` level.
+
+| **Field** | **Type** | **Description** |
+|--------------|-------------|-----------------------------------------------------------------------------------|
+| `scope` | `[]string` | Set of scopes the JWT will be validated against |
+
+### OAuth2Arguments
+
+**Appears on**: [FilterArguments][]
+Defines the input arguments that can be set for an [OAuth2 Filter][] on a per `FilterPolicy` `rule` level.
+
+| **Field** | **Type** | **Description** |
+|---------------------|-------------------------------------------------|-----------------------------------------------------------------------------------|
+| `scope` | `[]string` | Set of scopes the JWT will be validated against |
+| `insteadOfRedirect` | [OAuth2Redirect][] | Allows customizing the behavior of the OAuth2 redirect and whether it will redirect the browser or not. |
+| `sameSite` | `Enum`(`"default"`,`"none"`,`"lax"`,`"strict"`) | Set of options for setting the SameSite attribute on a cookie. [https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00][] for details. |
+
+### OAuth2Redirect
+
+**Appears on**: [OAuth2Arguments][]
+Allows customizing the behavior of the OAuth2 redirect and whether it will redirect the browser or not.
+
+| **Field** | **Type** | **Description** |
+|-------------------|-------------------------|-----------------------------------------------------------------------------------|
+| `statusCode` | `int` | The HTTP status code to be used in response. If filterRef is not set then this will default to a 403 forbidden. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Allows only applying the InsteadOfRedirect logic when a the header matches. |
+| `filterRefs` | \[\][FilterReference][] | List of references to Filter's that will be applied when an OAuth2 session has expired and the user would like to try a secondary authentication mechanism without redircting to the iDP. Nesting an [OAuth2 Filter][] inside of an [OAuth2 Filter][] is not supported. |
+
+### FilterPolicyStatus
+
+Automatically managed by the controller to reflect the state of the `FilterPolicy`
+
+| **Field** | **Type** | **Description** |
+|------------------------|---------------------------|-----------------------------------------------------------------------------------|
+| `conditions` | \[\][metav1.Condition][] | Describes the current condition of the `FilterPolicy`. |
+| `rules` |`[]FilterPolicyRuleStatus` | Describes the status for each unique Rule defined in the `Spec` |
+| `rules.index` | `string` | The zero-based index of the rule within `rules` with the problem to help with identifying the error |
+| `rules.host` | `string` | `host` of the rule with the problem to help with identifying the error
+| `rules.path` | `string` | `path` of the rule with the problem to help with identifying the error |
+| `rules.conditions` | \[\][metav1.Condition][] | Describes the current condition of a specific `rule`. |
+
+[FilterPolicy]: #filterpolicy
+[FilterPolicyRule]: #filterpolicyrule
+[FilterReference]: #filterreference
+[FilterArguments]: #filterarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[OAuth2Redirect]: #oauth2redirect
+[OAuth2Arguments]: #oauth2arguments
+[JWTArguments]: #jwtarguments
+[JWT Filter]: ../filter-jwt
+[OAuth2 Filter]: ../filter-oauth2
+[Filter custom resource]: ../filter
+[the v3alpha1 FilterPolicy api reference]: ../../../getambassador/v3alpha1/filterpolicy
+[corresponding Kubernetes validation]: https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
+[https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00]: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
+[metav1.Condition]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md
new file mode 100644
index 000000000..d2fcf7f35
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md
@@ -0,0 +1,84 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **WebApplicationFirewall** Resource (v1alpha1)
+
+The `WebApplicationFirewall` provides the configuration for an instance of a Web Application Firewall, and the
+[WebApplicationFirewallPolicy][] resource configures the matching patterns for when `WebApplicationFirewalls` get executed against requests.
+
+This doc is an overview of all the fields on the `WebApplicationFirewall` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+Tutorials and guides for Web Application Firewalls can be found in the [usage guides section][]
+
+
+ The WebApplicationFirewall resource was introduced more recently than the Filter and FilterPolicy resources, and does not have an older getambassador.io/v3alpha1 CRD version
+
+
+## WebApplicationFirewall API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewall
+metadata:
+ name: "example-waf"
+ namespace: "example-namespace"
+spec:
+ firewallRules: FirewallRules # required, One of configMapRef;file;http must be set below
+ sourceType: Enum # required
+ configMapRef: ConfigMapReference # optional
+ name: string # required
+ namespace: string # required
+ key: string # required
+ file: string # optional
+ http: # optional
+ url: string # required, must be a valid URL.
+ logging: # optional
+ onInterrupt: # required
+ enabled: bool # required
+status: # field managed by controller
+ conditions: []metav1.Condition
+```
+
+### WebApplicationFirewall
+
+| **Field** | **Type** | **Description** |
+|--------------------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `firewallRules` | [FirewallRules][] | Defines the rules to be used for the Web Application Firewall |
+| `logging.onInterrupt.enabled` | `bool` | When enabled, creates additional log lines in the $productName$ pods whenever the `WebApplicationFirewall` interrupts a request. This is in addition to the logging config that is available via the firewall configuration files. |
+
+### FirewallRules
+
+Defines the rules to be used for the Web Application Firewall
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `sourceType` | `Enum`(`"file"`,`"configmap"`,`"http"`) | Identifies which method is being used to load the firewall rules. Value must be one of `configMapRef`;`file`;`http`. The value corresponds to the following fields for configuring the selected method. |
+| `configMapRef` | [ConfigMapReference][] | Defines a reference to a [Kubernetes ConfigMap][] to load firewall rules from. |
+| `file` | `string` | Location of a file on disk to load the firewall rules from. Example: `"/ambassador/firewall/waf.conf"`. Files can be mounted to the $productName$ auth service deployment pods using a `ConfigMap`, or similar approach. |
+| `http.url` | `string` | URL to fetch firewall rules from. If the rules are unable to be downloaded/parsed from the provided url for whatever reason, the requests matched to this `WebApplicationFirewall` will be allowed/denied based on the configuration of the `onError` field. |
+
+### ConfigMapReference
+
+Defines a reference to a [Kubernetes ConfigMap][] to load firewall rules from.
+
+| **Field** | **Type** | **Description** |
+|--------------|------------|-----------------------------------------------|
+| `name` | `string` | Name of the referenced Kuberntes `ConfigMap`. |
+| `namespace` | `string` | Namespace of the referenced Kuberntes `ConfigMap`.|
+| `key` | `string` | The key in the referenced Kuberntes `ConfigMap` to pull the rules data from. |
+
+## Web Application Firewall Usage Guides
+
+The following guides will help you get started using Web Application Firewalls
+
+- [Using Web Application Firewalls][] - Get started using `WebApplicationFirealls` quickly
+- [Rules for Web Application Firewalls][] - Info about creating and configuring firewall rules
+- [Web Application Firewalls in Production][] - Recommendations and info for creating and running `WebApplicationFirewalls` in a production environment
+
+[FirewallRules]: #firewallrules
+[ConfigMapReference]: #configmapreference
+[usage guides section]: #web-application-firewall-usage-guides
+[WebApplicationFirewallPolicy]: ../webapplicationfirewallpolicy
+[Using Web Application Firewalls]: ../../../../howtos/web-application-firewalls
+[Rules for Web Application Firewalls]: ../../../../howtos/web-application-firewalls-config
+[Web Application Firewalls in Production]: ../../../../howtos/web-application-firewalls-in-production
+[Kubernetes ConfigMap]: https://kubernetes.io/docs/concepts/configuration/configmap/
diff --git a/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md
new file mode 100644
index 000000000..f9a8a7fcd
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md
@@ -0,0 +1,102 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **WebApplicationFirewallPolicy** Resource (v1alpha1)
+
+The `WebApplicationFirewallPolicy` resource configures the matching patterns for when [WebApplicationFirewalls][] get executed against requests; while the
+`WebApplicationFirewall` resource provides the configuration for an instance of a Web Application Firewall.
+
+This doc is an overview of all the fields on the `WebApplicationFirewallPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+Tutorials and guides for Web Application Firewalls can be found in the [usage guides section][]
+
+
+ The WebApplicationFirewallPolicy resource was introduced more recently than the Filter and FilterPolicy resources, and does not have an older getambassador.io/v3alpha1 CRD version
+
+
+## WebApplicationFirewallPolicy API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewallPolicy
+metadata:
+ name: "example-wafpolicy"
+ namespace: "example-namespace"
+spec:
+ rules: []WafMatchingRule # required
+ - host: string # optional, default: `"*"` (runs on all hosts)
+ path: string # optional, default: `"*"` (runs on all paths)
+ ifRequestHeader: HTTPHeaderMatch # optional
+ type: Enum # optional, default: `"Exact"`
+ name: string # required
+ value: string # optional
+ negate: bool # optional, default: `false`
+ wafRef: # required
+ name: string # required
+ namespace: string # required
+ onError: # optional
+ statusCode: int # required, min: `400`, max: `599`
+ precedence: int # optional
+status: # field managed by controller
+ conditions: []metav1.Condition
+ ruleStatuses:
+ - index: int
+ host: string
+ path: string
+ conditions: []metav1.Condition
+```
+
+### WebApplicationFirewallPolicy Spec
+
+| **Field** | **Type** | **Description** |
+|-----------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `rules` | \[\][WafMatchingRule][] | This object configures matching requests and executes WebApplicationFirewalls on them. Multiple different rules can be supplied in one `WebApplicationFirewallPolicy` instead of multiple separate `WebApplicationFirewallPolicy` resouurces if desired. |
+
+### WafMatchingRule
+
+| **Field** | **Type** | **Description** |
+|----------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `host` | `string` | A "glob-string" that matches on the `:authority` header of the incoming request. If not set, it will match on all incoming requests. |
+| `path` | `string` | A "glob-string" that matches on the request path. If not provided, then it will match on all incoming requests. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request header to determine if the `WebApplicationFirewall` is executed or not. |
+| `wafRef` | [WafReference][] | A reference to a `WebApplicationFirewall` to be applied against the request. |
+| `onError.statusCode` | `int` | Configure a response code to be sent to the downstream client when when a request matches the rule but there is a configuration or runtime error. By default, requests are allowed on error if this field is not configured. This covers runtime errors such as those caused by networking/request parsing as well as configuration errors such as if the `WebApplicationFirewall` that is referenced is misconfigured, cannot be found, or when its configuration cannot be loaded properly. Details about the errors can be found either in the `WebApplicationFirewall` status or container logs. |
+
+### HTTPHeaderMatch
+
+**Appears On**: [WafMatchingRule][]
+Checks if exact or regular expression matches a value in a request header to determine if the `WebApplicationFirewall` is executed or not.
+
+| **Field** | **Type** | **Description** |
+|------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `type` | `Enum`(`"Exact"`,`"RegularExpression"`) | Specifies how to match against the value of the header. Allowed values are `"Exact"`/`"RegularExpression"`. |
+| `name` | `string` | Name of the HTTP Header to be matched. Name matching MUST be case-insensitive. (See [https://tools.ietf.org/html/rfc7230#section-3.2][]) |
+| `value` | `string` | Value of HTTP Header to be matched. If type is `RegularExpression`, then this must be a valid regex with a length of at least 1. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. |
+
+### WafReference
+
+**Appears On**: [WafMatchingRule][]
+A reference to a `WebApplicationFirewall`
+
+| **Field** | **Type** | **Description** |
+|---------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `name` | Name of the `WebApplicationFirewall` being referenced
+| `namespace` | Namespace of the `WebApplicationFirewall`. This field is required. It must be a RFC 1123 label. Valid values include: `"example"`. Invalid values include: `"example.com"` - `"."` is an invalid character. The maximum allowed length is 63 characters, and the regex pattern `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` is used for validation. |
+
+## Web Application Firewall Usage Guides
+
+The following guides will help you get started using Web Application Firewalls
+
+- [Using Web Application Firewalls][]
+- [Rules for Web Application Firewalls][]
+- [Web Application Firewalls in Production][]
+
+[WafReference]: #wafreference
+[HTTPHeaderMatch]: #httpheadermatch
+[WafMatchingRule]: #wafmatchingrule
+[usage guides section]: #web-application-firewall-usage-guides
+[Using Web Application Firewalls]: ../../../../howtos/web-application-firewalls
+[Rules for Web Application Firewalls]: ../../../../howtos/web-application-firewalls-config
+[Web Application Firewalls in Production]: ../../../../howtos/web-application-firewalls-in-production
+[WebApplicationFirewalls]: ../webapplicationfirewall
+[https://tools.ietf.org/html/rfc7230#section-3.2]: https://tools.ietf.org/html/rfc7230#section-3.2
diff --git a/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-apikey.md b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-apikey.md
new file mode 100644
index 000000000..719f2d7a1
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-apikey.md
@@ -0,0 +1,76 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **APIKey Filter** Type (v3alpha1)
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied.
+For more information about how requests are matched to `Filter` resources and the order in which `Filters` are executed, please
+refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `APIKey Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `APIKey Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 APIKey Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## APIKey Filter API Reference
+
+To create an APIKey Filter, the `spec.type` must be set to `apikey`, and the `apikey` field must contain the configuration for your
+APIKey Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-apikey-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string
+ APIKey: APIKeyFilter # required
+ httpHeader: string # optional, default: `x-api-key`
+ keys: []APIKeyItem # required, min items: 1
+ - secretName: string # required
+```
+
+### APIKeyFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `httpHeader` | `string` | The name of the http header where the api-key will be found (always case-insensitive). By default it will use the `x-api-key` header. |
+| `keys` | \[\][APIKeyItem][] | The set of APIKeys that are used to check the whether the incoming request is valid. |
+
+### APIKeyItem
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `secretName` | `string` | Defines how to resolve the values of the keys. Currently the only supported way to resolve a key is via a local secret. APIKeys cannot use shared secrets in a different namespace than the `APIKey Filter` resource. |
+
+**Note about Secret formatting**:
+When supplying secrets to an API Key filter, the keys of the Secret do not matter, but the value of your API Key must be [base64][] encoded.
+
+For example, if you want to create a secret for the API Key value `example-api-key-value`, the secret should look like:
+
+```yaml
+---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apikey-filter-keys
+ type: Opaque
+ data:
+ any-name-you-want: ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+```
+
+You can specify as many API Keys in the Secret as you like.
+
+[APIKeyItem]: #apikeyitem
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 APIKey Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-apikey
+[base64]: https://en.wikipedia.org/wiki/Base64
diff --git a/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-external.md b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-external.md
new file mode 100644
index 000000000..53b2968f9
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-external.md
@@ -0,0 +1,110 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **External Filter** Type (v3alpha1)
+
+The `External Filter` allows users to provide their own Kubernetes Service speaking the [ext_authz protocol][].
+$productName$ will send a request to this "External Service" that contains a copy of the incoming request. The External Service will then be able
+to examine details of the incoming request, make changes to its headers, and allow or reject it by sending back a response to $productName$.
+The external service is free to perform any logic it likes before responding to $productName$, allowing for custom filtering and
+processing on incoming requests. The `External Filter` may be used along with any of the other Filter types. For more information about
+how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `External Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `External Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 External Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## External Filter API Reference
+
+To create an External Filter, the `spec.type` must be set to `external`, and the `external` field must contain the configuration for your
+external filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-external-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ External: ExternalFilter # required
+ auth_service: string # required
+ tls: bool # optional, default=true if auth_service starts with https://
+ tlsConfig: TLSConfig # optional
+ certificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ caCertificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ proto: Enum # optional, default=http
+ timeout_ms: int # optional, default=5000
+ allowed_request_headers: []string # optional
+ allowed_authorization_headers: []string # optional
+ add_linkerd_headers: bool # optional
+ path_prefix: string # optional
+ include_body: IncludeBody # optional
+ max_bytes: int # required
+ allow_partial: bool # required
+ protocol_version: Enum # required
+ status_on_error: # optional
+ code: int # required
+ failure_mode_allow: bool # optional
+
+```
+
+### ExternalFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `auth_service` | `string` | Identifies the external auth service to talk to. The format of this field is `scheme://host:port` where `scheme://` and `:port` are optional. The scheme-part, if present, must be either `http://` or `https://`; if the scheme-part is not present, it behaves as if `http://` is given. The scheme-part influences the default value of the `tls` field and the default value of the port-part. The host-part must be the [namespace-qualified DNS name][] of the service you want to use for authentication. |
+| `tls` | `bool` | Controls whether to use TLS or cleartext when speaking to the external auth service. The default is based on the scheme-part of the `auth_service` |
+| `tlsConfig` | [TLSConfig][] | Configures tls settings between $productName$ and the configured AuthService |
+| `proto` | `Enum` (`"http"`/`"grpc"`) | The type of [ext_authz protocol][] to use when communicating with the External Service. It is recommended to use "grpc" over "http" due to supporting additional capabilities. |
+| `timeout_ms` | `int` | The total maximum duration in milliseconds for the request to the external auth service, before triggering `status_on_error` or `failure_mode_allow` |
+| `allowed_request_headers` | `[]string` | Only applies when `proto: http`. Lists the headers (case-insensitive) that are copied from the incoming request to the request made to the external auth service. In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Cookie`, `From`, `Proxy-Authorization`, `User-Agent`, `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto` |
+| `allowed_authorization_headers` | `[]string` | Only applies when `proto: http`. Lists the headers (case-insensitive) that are copied from the response from the external auth service to the request sent to the upstream backend service (if the external auth service indicates that the request to the upstream backend service should be allowed). In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Location`, `Proxy-Authenticate`, `Set-cookie`, `WWW-Authenticate` |
+| `add_linkerd_headers` | `bool` | Only applies when `proto: http`. When true, in the request to the external auth service, adds an `l5d-dst-override` HTTP header that is set to the hostname and port number of the external auth service |
+| `path_prefix` | `string` | Only applies when `proto: http`. Prepends a string to the request path of the request when sending it to the external auth service. By default this is empty, and nothing is prepended. For example, if the client makes a request to `/foo`, and `path_prefix: /bar`, then the path in the request made to the external auth service will be `/foo/bar` |
+| `include_body` | [IncludeBody][] | Controls how much to buffer the request body to pass to the external auth service, for use cases such as computing an HMAC or request signature. If `include_body` is unset, then the request body is not buffered at all, and an empty body is passed to the external auth service. If include_body is not null, the `max_bytes` and `allow_partial` subfields are required. Unfortunately, in order for `include_body` to function properly, the `AuthService` resource must be edited to have its own `include_body` set with `max_bytes` greater than the largest `max_bytes` used by any `External Filter`, and `allow_partial: true` |
+| `status_on_error.code` | `int` | Controls the status code returned when unable to communicate with external auth service. This is ignored if `failure_mode_allow: true` |
+| `failure_mode_allow` | `bool` | Controls whether to allow or reject requests when there is an error communicating with the external auth service; a value of true allows the request through to the upstream backend service, a value of false returns a `status_on_error.code` response to the client |
+| `protocol_version` | `Enum (v3)` | Only applies when `proto: grpc`. Indicates the version of the transport protocol that the `External Filter` is using. Allowed values are `v3` and `v2`. `protocol_version` was used in previous versions of $productName$ to note the protocol used by the gRPC service for the `External Filter`. $productName$ 3.x is running an updated version of Envoy that has dropped support for the `v2` protocol, so starting in 3.x, if `protocol_version` is not specified, the default value of `v2` will cause an error to be posted and a static response will be returned. Therefore, you must set it to `protocol_version: v3`. If upgrading from a previous version, you will want to set it to `v3` and ensure it is working before upgrading to $productName$ 3.x. The default value for `protocol_version` remains `v2` in the `getambassador.io/v3alpha1` CRD specifications to avoid making breaking changes outside of a CRD version change |
+
+### IncludeBody
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `maxBytes` | `int` | Sets the number of bytes of the request body to buffer over to the External Service |
+| `allowPartial` | `bool` | Indicates whether the included body can be a partially buffered body or if the complete buffered body is expected. If not partial then a `HTTP 413` error is returned by Envoy. |
+
+### TLSConfig
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|-----------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `certificate.fromSecret` | SecretReference | Configures $productName$ to use the provided certificate to present to the server when connecting. Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret][] that contains the private key and public certificate that will be presented to the AuthService. Secret namespace defaults to Filter namespace if not set |
+| `caCertificate.fromSecret` | SecretReference | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret][] that contains the `tls.crt` key with the CA Certificate. Secret namespace defaults to Filter namespace if not set |
+
+
+[ExternalFilter]: #externalfilter
+[IncludeBody]: #includebody
+[TLSConfig]: #tlsconfig
+[ext_authz protocol]: ../../../../topics/running/services/ext-authz
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 External Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-external
+[Kubernetes Secret]: https://kubernetes.io/docs/concepts/configuration/secret
+[namespace-qualified DNS name]: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services
diff --git a/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-jwt.md b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-jwt.md
new file mode 100644
index 000000000..7bcb69479
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-jwt.md
@@ -0,0 +1,176 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **JWT Filter** Type (v3alpha1)
+
+The `JWT Filter` performs JWT validation on a [bearer token][] present in the HTTP header. If the bearer token JWT doesn't validate,
+or has insufficient scope, an RFC 6750-complaint error response with a `www-authenticate` header is returned. The list of acceptable
+signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in the `Filter` configuration. Only RSA and `none`
+algorithms are supported. For more information about how requests are matched to `Filter` resources and the order in which
+`Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `JWT Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `JWT Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 JWT Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## JWT Filter API Reference
+
+To create a JWT Filter, the `spec.type` must be set to `jwt`, and the `jwt` field must contain the configuration for your
+JWT Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-jwt-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ JWT: JWTFilter # required
+ jwksURI: string # required, (unless the only validAlgorithm is "none")
+ insecureTLS: bool # optional, default=false
+ renegotiateTLS: Enum # optional, default="never"
+ validAlgorithms: []string # optional, default is "all supported algos except for 'none'"
+ audience: string # optional (unless `requireAudience: true`)
+ requireAudience: bool # optional, default=false
+ issuer: string # optional (unless `requireIssuer: true`)
+ requireIssuer: bool # optional, default=false
+ requireExpiresAt: bool # optional, default=false
+ leewayForExpiresAt: Duration # optional
+ requireNotBefore: bool # optional, default=false
+ leewayForNotBefore: Duration # optional
+ requireIssuedAt: bool # optional, default=false
+ leewayForIssuedAt: Duration # optional
+ maxStale: Duration # optional
+ injectRequestHeaders: AddHeaderTemplate # optional
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ errorResponse: ErrorResponse # optional
+ contentType: "string" # deprecated, use 'headers' instead
+ realm: "string" # optional, default="{{.metadata.name}}.{{.metadata.namespace}}"
+ headers: # optional, default=[{name: "Content-Type", value: "application/json"}]
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ bodyTemplate: "string" # optional, default=`{{ . | json "" }}`
+```
+
+### JWTFilter
+
+| **Field** | **Type** | **Description** |
+|-------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `jwksURI` | `string` | A URI that returns the JWK Set per RFC 7517. This is required unless validAlgorithms=["none"], in that case verifying the signature of the token is disabled. This is considered unsafe and is discouraged when receiving tokens from untrusted sources. |
+| `validAlgorithms` | \[\][ValidAlgorithms][](`Enum`) | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `audience` | `string` | Identifies the recipient that the JWT is intended for and will be used to validate the provided token is intended for the configured audience. If not provided then `aud` claim on incoming token is not validated and will be considered valid. If `aud` is unset on the token by default it will be considered valid even if it doesn't match the audience value. To enforce that a token has the aud claim, then set `requireAudience: true`. |
+| `requireAudience` | `bool` | Modifies the validation behavior for when the audience claim (aud) is unset on the incoming token. `false` (default) => if aud claim is unset then claim is considered valid. `true` => if aud claim is unset then claim/token are invalid |
+| `issuer` | `string` | Identifies the expected AuthorizationServer that isssued the token. If not provided then the issuer claim will not be validated. If `issuer` is unset on the token by default it will be considered valid even if it doesn't match the expected issuer value. To enforce that a token has the issuer claim, then set `requireIssuer: true`. |
+| `requireIssuer` | `bool` | Modifies the validation behavior for when the issuer claim (iss) is unset on the incoming token. `false` (default) => if aud claim is unset on incoming token then claim is considered valid `true` => if exp claim is unset then claim is invalid |
+| `requireExpiresAt` | `bool` | Modifies the validation behavior for when the expiresAt claim (exp) is unset on the incoming token. `false` (default) => if exp claim is unset on incoming token then claim is valid `true` => if exp claim is unset then claim/token are invalid |
+| `leewayForExpiresAt` | [Duration][] | Allows token expired by this much to still be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `requireNotBefore` | `bool` | Modifies the validation behavior for when the not before time claim (nbf) is unset on the incoming token. `false` (default) => if `nbf` claim is unset on incoming token then claim is valid `true` => if `nbf` claim is unset then claim/token are invalid |
+| `leewayForNotBefore` | [Duration][] | Allows tokens that shouldn't be used until this much in the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `requireIssuedAt` | `bool` | Modifies the validation behavior for when the issuedAt claim (iat) is unset on the incoming token. `false` (default) => if `iat` claim is unset on incoming token then claim is valid `true` => if `iat` claim is unset then claim/token are invalid |
+| `leewayForIssuedAt` | [Duration][] | Allows tokens issued by this much into the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `injectRequestHeaders` | \[\][AddHeaderTemplate][] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream request headers as values. For example, attaching user email claim to a header from the token. |
+| `maxStale` | [Duration][] | Sets the duration that JWKs keys and OIDC discovery responses will be cached, ignoring any caching headers when configured |
+| `insecureTLS` | `bool` | Disables TLS verification for cases when jwksURI begins with `https://`. |
+| `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the JWTFilter will renegotiateTLS with the `jwksURI` server and if so what supported method of renegotiation will be used. |
+| `errorResponse` | [CustomErrorResponse][] | Allows setting a custom Response to the downstream client when an invalid JWT is received. |
+
+### Duration
+
+**Appears On**: [JWTFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ValidAlgorithms
+
+**Appears On**: [JWTFilter][]
+The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not
+in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP,
+as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided
+to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected.
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+
+### AddHeaderTemplate
+
+**Appears On**: [JWTFilter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream
+request headers as values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+`value` is the value of the header to set and is evaluated as a special GoLang Template.
+This allows the header value to be set based on the JWT value. The value is specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.token.Raw` → The raw JWT (`string`)
+- `.token.Header` → The JWT header, as parsed JSON (`map[string]interface{}`)
+- `.token.Claims` → The JWT claims, as parsed JSON (`map[string]interface{}`)
+- `.token.Signature` → The token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+Also available to the template are [the standard functions available to Go text/templates][], as well as:
+
+- a `hasKey` function that takes the a string-indexed map as arg1, and returns whether it contains the key arg2. (This is the same as [the Sprig function of the same name][].)
+- a `doNotSet` function that causes the result of the template to be discarded, and the header field to not be adjusted. This is useful for only conditionally setting a header field; rather than setting it to an empty string or `""`. Note that this does not unset an existing header field of the same name and could be a potential security vulnerability depending on how this is used if an untrusted client spoofs these headers.
+
+
+ Any headers listed will override (not append to) the original request header with that name.
+
+
+### CustomErrorResponse
+
+**Appears On**: [JWTFilter][]
+Allows setting a custom Response to the downstream client when an invalid JWT is received.
+
+| **Field** | **Type** | **Description** |
+|------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `realm` | `string` | Indicates the scope of protection or the application that is checking the token. By default, this is set to the fully qualified name of the `JWT Filter` as `"{name}.{namespace}"` to identify which filter rejected the error. This can be overriden to provide more relevant information to end-users. |
+| `bodyTemplate` | `string` (GoLang Template) | Golang `text/template` string that will be evaluated and used to build the format returned. |
+| `headers` | \[\][AddHeaderTemplate][] | Allows providing additional http response headers for the error response. The current maximum is 16 headers, which aligns with the Gateway-API and modified headers on HTTPRoutes. |
+
+`bodyTemplate` specifies body of the error response returned to the downstream client; specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.status_code` → The HTTP status code to be returned (`int`)
+- `.httpStatus` → An alias for .status_code (`int`, hidden from `{{ . | json "" }}`)
+- `.message` → The error message (`string`)
+- `.error` → The raw Go error object that generated .message (`error`, hidden from `{{ . | json "" }}`)
+- `.error.ValidationError` → The JWT validation error, will be nil if the error is not purely JWT validation (`jwt.ValidationError` insufficient scope, malformed or missing Authorization header)
+- `.request_id` → The Envoy request ID, for correlation (`string`, hidden from `{{ . | json "" }}` unless `.status_code` is in the `5xx` range)
+- `.requestId` → An alias for .request_id (`string`, hidden from `{{ . | json "" }}`)
+
+Also availabe to the template are [the standard functions available to Go text/templates][], as well as:
+
+- A `json` function that formats arg2 as JSON, using the arg1 string as the starting indentation. For example, the template `{{ json "indent>" "value" }}` would yield the string `indent>"value"`.
+
+[JWTFilter]: #jwtfilter
+[ValidAlgorithms]: #validalgorithms
+[AddHeaderTemplate]: #addheadertemplate
+[CustomErrorResponse]: #customerrorresponse
+[Duration]: #duration
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 JWT Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-jwt
+[bearer token]: https://datatracker.ietf.org/doc/html/rfc6750
+[Go text/template string]: https://pkg.go.dev/text/template
+[the standard functions available to Go text/templates]: https://pkg.go.dev/text/template#hdr-Functions
+[the Sprig function of the same name]: https://masterminds.github.io/sprig/dicts.html#haskey
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-oauth2.md b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-oauth2.md
new file mode 100644
index 000000000..0b55315a4
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-oauth2.md
@@ -0,0 +1,368 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **OAuth2 Filter** Type (v3alpha1)
+
+The OAuth2 Filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+
+
+
+This doc is an overview of all the fields on the `OAuth2 Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `OAuth2 Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 OAuth2 Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## OAuth2 Filter API Reference
+
+To create an OAuth2 Filter, the `spec.type` must be set to `oauth2`, and the `oauth2` field must contain the configuration for your
+OAuth2 filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-oauth2-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ OAuth2: OAuth2 # required
+ authorizationURL: string # required
+
+ #------------------------------------------------------------------#
+ # OAuth Client settings #
+ #------------------------------------------------------------------#
+ expirationSafetyMargin: Duration # optional
+ grantType: Enum # optional, default="AuthorizationCode"
+ clientAuthentication: # optional
+ method: "enum" # optional, default="HeaderPassword"
+ jwtAssertion: # optional if `method: "JWTAssertion"`, forbidden otherwise
+ setClientID: bool # optional, default=false
+ audience: string # optional, default is to use the token endpoint from the authorization URL
+ signingMethod: Enum # optional, default="RS256"
+ lifetime: Duration # optional, default="1m"
+ setNBF: bool # optional, default=false
+ nbfSafetyMargin: Duration # optional, default=0s
+ setIAT: bool # optional, default=false
+ otherClaims: # optional, default={}
+ "string": anything
+ otherHeaderParameters: # optional; default={}
+ "string": anything
+
+ ## OAuth Client settings when `grantType: "AuthorizationCode"`
+ clientURL: string # deprecated, use 'protectedOrigins' instead
+ protectedOrigins: []ProtectedOrigin # required, minItems: 1
+ - origin: string # required
+ internalOrigin: string # optional, default is to just use the 'origin' field
+ includeSubdomains: bool # optional, default=false
+ useSessionCookies: # optional, default={ value: false }
+ value: bool # optional, default=true
+ ifRequestHeader: # optional, default to apply "useSessionCookies.value" to all requests
+ name: string # required
+ negate: bool # optional, default=false
+ value: string # optional, default is any non-empty string
+ valueRegex: string # optional, default is any non-empty string
+ clientSessionMaxIdle: Duration # optional, default is to use the access token lifetime or 14 days if a refresh token is present
+ postLogoutRedirectURI: string # optional
+ extraAuthorizationParameters: map[string]string # optional, default={}
+ "string": "string"
+
+ ## OAuth Client settings when `grantType: "AuthorizationCode"/"Password"`
+ clientID: string # required
+ secret: string # required (unless secretName is set)
+ secretName: string # required (unless secret is set)
+ secretNamespace: string # optional, default is the same namespace as the Filter
+
+ #------------------------------------------------------------------#
+ # OAuth Resource Server settings #
+ #------------------------------------------------------------------#
+ allowMalformedAccessToken: bool # optional, default=false
+ accessTokenValidation: Enum # optional, default="auto"
+ accessTokenJWTFilter: # optional
+ name: string # required
+ namespace: string # optional, default is the same namespace as the Filter
+ inheritScopeArgument: bool # optional, default=false
+ stripInheritedScope: bool # optional, default=false
+ arguments: JWTFilterArguments # optional
+ injectRequestHeaders: # optional
+ - name: string # required
+ value: string # required
+
+ #------------------------------------------------------------------#
+ # HTTP client settings for talking with the identity provider #
+ #------------------------------------------------------------------#
+ insecureTLS: bool # optional, default=false
+ renegotiateTLS: Enum # optional, default="never"
+ maxStale: Duration # optional, default="0"
+```
+
+### OAuth2Filter
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `authorizationURL` | `string` | Identity Provider Issuer URL which hosts the OpenID provider well-known configurartion. The URL must be an absolute URL. Per [OpenID Connect Discovery 1.0][] the configuration must be provided in a json document at the path `/.well-known/openid-configuration`. This is used by the OAuth2 Filter for determining things like the AuthorizationEndpoint, TokenEndpoint, JWKs endpint, etc... |
+| `expirationSafetyMargin` | [Duration][] | Sets a buffer to check if the Token is expired or is going to expire within the safety margin. This is to ensure the application has enough time to reauthenticate to adjust for clock skew and network latency. By default, no safety margin is added. If a token is received with an expiration less than this field, then the token is considered to already be expired. |
+| `grantType` | `Enum`(`"AuthorizationCode"`,`"ClientCredentials"`,`"Password"`,`"ResourceOwner"`) | Sets the Authorization Flow that the filter will use to authenticate the incoming request. |
+| `clientAuthentication` | [ClientAuthentication][] | Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password. |
+| `protectedOrigins` | \[\][ProtectedOrigin][] | (You determine these, and must register them with your identity provider) Identifies hostnames that can appropriately set cookies for the application. Only the scheme (`https://`) and authority (`example.com:1234`) parts are used; the path part of the URL is ignored |
+| `useSessionCookies` | [SessionCookies][] | By default, any cookies set by $productName$ will be set to expire when the session expires naturally. `useSessionCookies` may be used to cause session cookies to be used instead |
+| `clientSessionMaxIdle` | [Duration][] | Controls how long the session held by $productName$'s OAuth client will last until we automatically expire it. $productName$ creates a new session when submitting requests to the upstream backend server and sets a cookie containing the sessionID. When a user makes a request to a backend service protected by the OAuth2 Filter, the OAuth Client in Ambassador Edge Stack will use the sessionID contained in the cookie to fetch the access token (and optional refresh token) for the current session so that it can be used when submitting a request to the upstream backend service. This session has a limited lifetime before it expires or extended, prompting the user to log back in. Setting a `clientSessionMaxIdle` duration is useful when your IdP is configured to return a refresh token along with an access token from your IdP's authorization server. `clientSessionMaxIdle` can be set to match Ambassador Edge Stack OAuth client's session lifetime to the lifetime of the refresh token configured within the IdP. If this is not set, then we tie the OAuth client's session lifetime to the lifetime of the access token received from the IdP's authorization server when no refresh token is also provided. If there is a refresh token, then by default we set it to be 14 days |
+| `postLogoutRedirectURI` | `string` | Set this field to a valid URL to have $productName$ redirect there upon a successful logout. You must register the following endpoint with your IDP as the Post Logout Redirect `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect`. This informs your IDP to redirect back to $productName$ once the IDP has cleared the session data. Once the IDP has redirected back to $productName$, this clears the local $productName$ session information before redirecting to the destination specified by the `postLogoutRedirectURI` value. If Post Logout Redirect is configured in your IDP to `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` then, after a successful logout, a redirect is issued to the URL configured in `postLogoutRedirectURI`. If `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` is configured as the Post Logout Redirect in your IDP, but `postLogoutRedirectURI` is not configured in $productName$, then your IDP will error out as it will be expecting specific instructions for the post logout behavior. Refer to your IDP’s documentation to verify if it supports Post Logout Redirects. For more information on `post_logout_redirect_uri functionality`, refer to the [OpenID Connect RP-Initiated Logout 1.0 specs](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) |
+| `extraAuthorizationParameters`| `map`\[`string`\]`string` | Extra (non-standard or extension) OAuth authorization parameters to use. It is not valid to specify a parameter used by OAuth itself ("response_type", "client_id", "redirect_uri", "scope", or "state") |
+| `clientID` | `string` | The Client ID you get from your identity provider |
+| `secret` | `string` | The client secret you get from your identity provider as a string. It is invalid to configure both `secret` and `secretName` |
+| `secretName` | `string` | The client secret you get from your identity provider as a Kubernetes `generic` Secret, named by `secretName`/`secretNamespace`. The Kubernetes secret must of the `generic` type, with the value stored under the key`oauth2-client-secret`. If `secretNamespace` is not given, it defaults to the namespace of the Filter resource. It is invalid to configure both `secret` and `secretName` |
+| `secretNamespace` | `string` | The client secret you get from your identity provider as a Kubernetes `generic` Secret, named by `secretName`/`secretNamespace`. The Kubernetes secret must of the `generic` type, with the value stored under the key`oauth2-client-secret`. If `secretNamespace` is not given, it defaults to the namespace of the Filter resource. It is invalid to configure both `secret` and `secretName` |
+| `allowMalformedAccessToken` | `bool` | Allow any access token, even if they are not RFC 6750-compliant. |
+| `accessTokenValidation` | `Enum`(`"jwt"`,`"userinfo"`,`"auto"`) | How to verify the liveness and scope of Access Tokens issued by the identity provider. Empty or unset is equivalent to `"auto"` |
+| `accessTokenJWTFilter` | [AccessTokenJWTFilter][] | Used to identify a JWT Filter to use for validating access token JWTs. It is an error to point at a Filter that is not a JWT filter |
+| `injectRequestHeaders` | \[\][AddHeaderTemplate][] | injects HTTP header fields in to the request before sending it to the upstream service; where the header value can be set based on the JWT value. If an OAuth2 filter is chained with a JWT filter with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the OAuth2 filter will populate the value. The value is specified as a Go `text/template` string |
+| `insecureTLS` | `bool` | disables TLS verification when speaking to an identity provider with an `https://` `authorizationURL`. This is discouraged in favor of either using plain `http://` or [installing a self-signed certificate][] |
+| `renegotiateTLS` | `Enum`(`"never"`,`"onceAsClient"`,`"freelyAsClient"`) | Allows a remote server to request TLS renegotiation |
+| `maxStale` | [Duration][] | How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also **ignores the `no-store` and `no-cache` Cache-Control directives on responses**. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Setting to 0 means that it will default back to the identity provider's default cache settings as specified by the Cache-Control directives on responses which may include no caching depending if the identity provider sets the `no-cache` and `no-store` directives. Note that if you are reusing the same `authorizationURL` and `jwksURI` across different OAuth and JWT filters respectively, then you MUST set `maxStale` as a consistent value on each filter to get predictable caching behavior |
+
+**`grantType` options**:
+
+- `"AuthorizationCode"`: Authenticate by redirecting to a login page served by the identity provider.
+- `"Password"`: Authenticate by requiring `X-Ambassador-Username` and `X-Ambassador-Password` on all incoming requests, and use them to authenticate with the identity provider using the OAuth2 Resource Owner Password Credentials grant type.
+- `"ClientCredentials"`: Authenticate by requiring that the incoming HTTP request include as headers the credentials for Ambassador to use to authenticate to the identity provider.
+ - The type of credentials needing to be submitted depends on the `clientAuthentication.method` (below):
+ - For `"HeaderPassword"` and `"BodyPassword"`, the headers `X-Ambassador-Client-ID` and `X-Ambassador-Client-Secret` must be set.
+ - For `"JWTAssertion"`, the `X-Ambassador-Client-Assertion` header must be set to a JWT that is signed by your client secret, and conforms with the requirements in RFC 7521 section 5.2 and RFC 7523 section 3, as well as any additional specified by your identity provider.
+
+**`accessTokenValidation` options**:
+
+- `"jwt"`: Validates the Access Token as a JWT.
+
+ - By default: It accepts the RS256, RS384, or RS512 signature algorithms, and validates the signature against the JWKS from
+OIDC Discovery. It then validates the `exp`, `iat`, `nbf`, `iss` (with the Issuer from OIDC Discovery), and `scope` claims: if present,
+none of the scope values are required to be present. This relies on the identity provider using non-encrypted signed JWTs as
+Access Tokens, and configuring the signing appropriately
+ - This behavior can be modified by delegating to [JWT Filter][] with `accessTokenJWTFilter`:
+
+- `"userinfo"`: Validates the access token by polling the OIDC UserInfo Endpoint. This means that $productName$ must initiate
+an HTTP request to the identity provider for each authorized request to a protected resource. This performs poorly,
+but functions properly with a wider range of identity providers. It is not valid to set `accessTokenJWTFilter` if
+`accessTokenValidation`: `userinfo`.
+
+- `"auto"` attempts to do `"jwt"` validation if any of these conditions are true:
+ - `accessTokenJWTFilter` is set
+ - `grantType` is `"ClientCredentials"`
+ - the Access Token parses as a JWT and the signature is valid,
+ - If none of the above conditions are satisfied, it falls back to `"userinfo"` validation.
+
+### Duration
+
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ClientAuthentication
+
+Configures how Ambassador uses the `clientID` and `secret` to authenticate itself to the identity provider
+
+| **Field** | **Type** | **Description** |
+|----------------|----------------------------------|---------------------------------------------------------------------------------------------------------|
+| `method` | `Enum`(`"HeaderPassword"`,`"BodyPassword"`,`"JWTAssertion"`) | Defines the type of client authentication that will be used |
+| `jwtAssertion` | [JWTAssertion][] | This field is only used when `method: "JWTAssertion"`. Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow. |
+
+`method` options:
+
+- `"HeaderPassword"`: Treat the client secret as a password, and pack that in to an HTTP header for HTTP Basic authentication.
+- `"BodyPassword"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+- `"JWTAssertion"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+
+### JWTAssertion
+
+Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|-------------------------|---------------------------------------------------------------------------------------------------------|
+| `setClientID` | `bool` | Whether to set the Client ID as an HTTP parameter; setting it as an HTTP parameter is optional (per RFC 7521 §4.2) because the Client ID is also contained in the JWT itself, but some identity providers document that they require it to also be set as an HTTP parameter anyway. |
+| `audience` | `string` | This field is ignored when `grantType: "ClientCredentials"`. The audience your IDP requires for authentication. If not set then the default will be to use the token endpoint from the OIDC discovery document. |
+| `signingMethod` | [ValidAlgorithms][] | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `lifetime` | [Duration][] | This field is ignored when `grantType: "ClientCredentials"`. The lifetime of the generated JWT; just enough time for the request to the identity provider to complete (plus possibly an extra allowance for clock skew). |
+| `setNBF` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "nbf" ("Not Before") claim in the generated JWT. |
+| `nbfSafetyMargin` | [Duration][] | This field is only used when `setNBF: true` The safety margin to build-in to the "nbf" claim, to allow for clock skew between ambassador and the identity provider. |
+| `setIAT` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "iat" ("Issued At") claim in the generated JWT. |
+| `otherClaims` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Key/value pairs that will be add to the JWT sent for client Auth to the Identity Provider |
+| `otherHeaderParameters` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Any extra JWT header parameters to include in the generated JWT non-standard claims to include in the generated JWT; only the "typ" and "alg" header parameters are set by default. |
+
+### ValidAlgorithms
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+ - The secret must be a PEM-encoded Eliptic Curve private key
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+ - The secret is a raw string of bytes; it can contain anything
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+ - The secret must be a PEM-encoded RSA private key
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+ - The secret must be a PEM-encoded RSA private key
+
+### ProtectedOrigin
+
+You determine these, and must register them with your identity provider. Identifies hostnames that can
+appropriately set cookies for the application. Only the scheme (`https://`) and authority (`example.com:1234`) parts are used; the
+path part of the URL is ignored. You will need to register each origin in `protectedOrigins` as an authorized callback endpoint with your identity provider. The URL
+will look like `{{ORIGIN}}/.ambassador/oauth2/redirection-endpoint`.
+
+
+
+
+If you provide more than one `protectedOrigin`, all share the same
+authentication system, so that logging into one origin logs you
+into all origins; to have multiple domains that have separate
+logins, use separate `Filter`s.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|------------|---------------------------------------------------------------------------------------------------------|
+| `origin` | `string` | The absolute URL (schema://hostname) that is protected by the OAuth2 Filter |
+| `includeSubdomains` | `bool` | Enables protecting sub-domains of the domain identified in the Origin field. Example, when `Origin=https://example.com` then the subdomain of `https://app.example.com` would be watched. |
+| `allowedInternalOrigins` | `[]string` | Indentifies a list of allowed internal origins that were set by a downstream proxy via a host header rewrite. The origins identified in this list ensures the request is allowed and will ensure it redirects correctly to the upstream origin. For example, a downstream client will communicate with an origin of `https://example.com` but then an internal proxy will do a rewrite so that the host header received by Edge Stack is `http://example.internal`. |
+
+**Note about `allowedInternalOrigins`**: This field is primarily used to allow you to tell $productName$ that there is another gateway
+in front of $productName$ that rewrites the Host header, so that on the internal network between that gateway and $productName$, the
+origin appears to be `allowedInternalOrigins` instead of `origin`. As a special-case the scheme and/or authority of the `allowedInternalOrigins`
+may be `"*"`, which matches any scheme or any domain respectively.
+Using `"*"` is most useful in configurations with exactly one protected origin; in such a configuration, $productName$ doesn't need
+to know what the origin looks like on the internal network, just that a gateway in front of $productName$ is rewriting it.
+It is invalid to use `"*"` with `includeSubdomains: true`.
+
+For example, if you have a gateway in front of $productName$ handling traffic for `myservice.example.com`, terminating TLS and routing
+that traffic to $productName$ with the name `example.internal`, you might write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - http://example.internal
+```
+
+or, to avoid being fragile to renaming example.internal to something else, since there are not multiple origins that the `Filter` must
+distinguish between, you could instead write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - "*://*"
+```
+
+### AddHeaderTemplate
+
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+The header value can be set based on the JWT value. If an `OAuth2 Filter` is chained with a [JWT filter][] with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the `OAuth2 Filter` will populate the value. The value is specified as a [Go text/template][] string, with the following data made available to it:
+
+- `.token.Raw` → The access token raw JWT (`string`)
+- `.token.Header` → The access token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.token.Claims` → The access token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.token.Signature` → The access token signature (`string`)
+- `.idToken.Raw` → The raw id token JWT (`string`)
+- `.idToken.Header` → The id token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Claims` → The id token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Signature` → The id token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+### SessionCookies
+
+By default, any cookies set by the $productName$ will be set to expire when the session expires naturally. The
+`useSessionCookies` setting may be used to cause session cookies to be used instead.
+
+
+
+- Normally cookies are set to be deleted at a specific time; session cookies are deleted whenever the user closes their web
+browser. This may mean that the cookies are deleted sooner than normal if the user closes their web browser; conversely, it may
+mean that cookies persist for longer than normal if the use does not close their browser.
+- The cookies being deleted sooner may or may not affect user-perceived behavior, depending on the behavior of the identity provider.
+- Any cookies persisting longer will not affect behavior of the system; Ambassador Edge Stack validates whether the session is expired when considering the cookie.
+
+If `useSessionCookies` is non-`null`, then:
+
+- By default it will have the cookies for all requests be session cookies or not according to the `useSessionCookies.value` sub-argument.
+- Setting the `useSessionCookies.ifRequestHeader` sub-argument tells it to use `useSessionCookies.value` for requests that match the condition, and `!useSessionCookies.value` for requests don't match.
+
+When determining if a request matches, it looks at the HTTP header field named by `useSessionCookies.ifRequestHeader.name` (case-insensitive), and checks if it is either set to (if `useSessionCookies.ifRequestHeader.negate: false`) or not set to (if `useSessionCookies.ifRequestHeader.negate: true`)...
+
+- a non-empty string (if neither `useSessionCookies.ifRequestHeader.value` nor `useSessionCookies.ifRequestHeader.valueRegex` are set)
+- the exact string `value` (case-sensitive) (if `useSessionCookies.ifRequestHeader.value` is set)
+- a string that matches the regular expression `useSessionCookies.ifRequestHeader.valueRegex` (if `valueRegex` is set). This uses [RE2][] syntax (always, not obeying `regex_type` in the `Module`) but does not support the `\C` escape sequence.
+- (it is invalid to have both `value` and `valueRegex` set)
+
+| **Field** | **Type** | **Description** |
+|-------------------|---------------------------|-----------------------------------------------------------------------------------|
+| `value` | `bool` |
+| `ifRequestHeader` | [HTTPHeaderMatch][] |
+
+### HTTPHeaderMatch
+
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of the HTTP Header to be matched. Only one of `value` or `valueRegex` can be configured |
+| `valueRegex` | `string` | Regex expression for matching the value of the HTTP Header. Only one of `value` or `valueRegex` can be configured. This uses [RE2][] syntax (always, not obeying `regex_type` in the `ambassador Module`) but does not support the `\C` escape sequence. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### AccessTokenJWTFilter
+
+**Appears On**: [OAuth2Filter][]
+Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes
+
+| **Field** | **Type** | **Description** |
+|------------------------|-------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `namespace` | `string` | Namespace of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `inheritScopeArgument`:| `bool` | Will use the same scope as set on the FilterPolicy OAuth2Arguments. If the JWTFilter sets a scope as well then the union of the two will be used. |
+| `stripInheritedScope` | `bool` | Determines whether or not to santized a scope that is formatted as an URI and was inherited from the FilterPolicy OAuth2Arguments. This will be done prior to passing it along to the referenced JWTFilter. This requires that InheritScopeArgument is true. |
+| `arguments` | [JWTArguments][] | Defines the input arguments that can be set for a JWTFilter. |
+
+### JWTArguments
+
+Defines the input arguments that can be set for a JWTFilter.
+
+| **Field** | **Type** | **Description** |
+|------------|-------------|---------------------------------------------------------------------------------------------------------|
+| `scope` | `[]string` | A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope. |
+
+**Some notes about `scope`**:
+
+- If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed.
+- If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+- As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+- The ordering of scope values does not matter, and is ignored.
+
+[AddHeaderTemplate]: #addheadertemplate
+[Oauth2Filter]: #oauth2filter
+[AccessTokenJWTFilter]: #accesstokenjwtfilter
+[ClientAuthentication]: #clientauthentication
+[JWTArguments]: #jwtarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[JWTAssertion]: #jwtassertion
+[ValidAlgorithms]: #validalgorithms
+[ProtectedOrigin]: #protectedorigin
+[SessionCookies]: #sessioncookies
+[Duration]: #duration
+[installing a self-signed certificate]: ../../../../topics/using/filters/#filters-using-self-signed-certificates
+[JWT Filter]: ../filter-jwt
+[the v1alpha1 OAuth2 Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-oauth2
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
+[OpenID Connect Discovery 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
+[RE2]: https://github.com/google/re2/wiki/Syntax
diff --git a/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-plugin.md b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-plugin.md
new file mode 100644
index 000000000..264ed57b2
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter-plugin.md
@@ -0,0 +1,42 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Plugin Filter** Type (v3alpha1)
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled into a .so file,
+which you load into the Envoy Proxy container at `/etc/ambassador-plugins/${NAME}.so`. For more information about how requests are
+matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `Plugin Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `Plugin Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 Plugin Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## Plugin Filter API Reference
+
+To create a Plugin Filter, the `spec.type` must be set to `plugin`, and the `plugin` field must contain the configuration for your Plugin Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-plugin-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ plugin: PluginFilter # required
+ name: string # required
+```
+
+`name`: Indicates the compiled binaries name excluding the extension for the Plugin.
+Envoy Proxy will look for the .so file in the `/etc/ambassador-plugins` directory.
+For example, if `name: "example-plugin"` the .so file should be available at
+`"/etc/ambassador-plugins/example-plugin.so"` on the Envoy Proxy container.
+
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 Plugin Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-plugin
diff --git a/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter.md b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter.md
new file mode 100644
index 000000000..fb65cb023
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filter.md
@@ -0,0 +1,64 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **Filter** Resource (v3alpha1)
+
+The `Filter` custom resource works in conjunction with the [FilterPolicy custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending them to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests, such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute against those requests. Filters are largely used to add built-in authentication and security, but
+$productName$ also supports developing custom filters to add your own processing and logic.
+
+This doc is an overview of all the fields on the `Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 Filter api reference][].
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## v3alpha1 Filter API Reference
+
+Filtering is configured using `Filter` custom resources. The body of the resource `spec` depends on the filter type:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ JWT: JWTFilter # optional
+ OAuth2: OAuth2Filter # optional
+ APIKey: APIKeyFilter # optional
+ External: ExternalFilter # optional
+ plugin: PluginFilter # optional
+```
+
+### FilterSpec
+
+Other than `ambassador_id`, only one of the following fields may be configured. For example you cannot create a `Filter` with both
+`JWT` and `External`.
+
+| **Field** | **Type** | **Description** |
+|------------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------|
+| `ambassador_id` | \[\]`string` | Ambassador id accepts a list of strings that allow you to restrict which instances of $productName$ can use/view this resource. If `ambassador_id` is configured, then only Deployments of $productName$ with a matching `AMBASSADOR_ID` environment variable will be able to use this resource. |
+| `JWT` | [JWTFilter][] | Provides configuration for the JWT Filter type |
+| `OAuth2` | [OAuth2Filter][] | Provides configuration for the OAuth2 Filter type |
+| `APIKey` | [APIKeyFilter][] | Provides configuration for the APIKey Filter type |
+| `External` | [ExternalFilter][] | Provides configuration for the External Filter type |
+| `Plugin` | [PluginFilter][] | Provides configuration for the Plugin Filter type |
+
+[FilterPolicy custom resource]: ../filterpolicy
+[JWTFilter]: ../filter-jwt
+[PluginFilter]: ../filter-plugin
+[OAuth2Filter]: ../filter-oauth2
+[APIKeyFilter]: ../filter-apikey
+[ExternalFilter]: ../filter-external
+[the v1alpha1 Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter
diff --git a/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filterpolicy.md b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filterpolicy.md
new file mode 100644
index 000000000..af34f45ab
--- /dev/null
+++ b/docs/edge-stack/3.9/custom-resources/getambassador/v3alpha1/filterpolicy.md
@@ -0,0 +1,139 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **FilterPolicy** Resource (v3alpha1)
+
+The `FilterPolicy` custom resource works in conjunction with the [Filter custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute on those requests.
+
+
+
+This doc is an overview of all the fields on the `FilterPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `FilterPolicy` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 FilterPolicy api reference][].
+
+
+ v3alpha1FilterPolicies can only be reference v3alpha1Filters.
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## FilterPolicy API Reference
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec: FilterPolicy
+ ambassador_id: []string # optional
+ rules: []FilterPolicyRule # required, minItems: 1
+ - host: string # required
+ path: string # required
+ precedence: int # optional
+ filters: []FilterReference # required, minItems: 1
+ - name: string # required
+ namespace: string # optional, default is the same namespace as the FilterPolicy
+ onDeny: Enum # optional, default="break"
+ onAllow: Enum # optional, default="continue"
+ ifRequestHeader: HTTPHeaderMatch # optional
+ name: string # required
+ value: string # optional, default is any non-empty string
+ valueRegex: string # optional, default is any non-empty string
+ negate: bool # optional, default=false
+ arguments: FilterArguments # optional
+```
+
+### FilterPolicy
+
+| **Field** | **Type** | **Description** |
+|------------------|----------------------------|-----------------------------------------------------------------------------------|
+| `ambassador_id` | \[\]`string` | Ambassador id accepts a list of strings that allow you to restrict which instances of $productName$ can use/view this resource. If `ambassador_id` is configured, then only Deployments of $productName$ with a matching `AMBASSADOR_ID` environment variable will be able to use this resource. |
+| `rules` | \[\][FilterPolicyRule][] | Set of matching rules that are checked against incoming request to determine which set of Filter's to apply. If no matches are found then the request is allowed through to the upstream service without executing any Filters. |
+
+### FilterPolicyRule
+
+Configures matching rules that are checked against incoming request to determine which `Filter` to apply (if any).
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-----------------------------------------------------------------------------------|
+| `host` | `string` | "glob-string" that matches on the `:authority` header of the incoming request. If not set it will match on all incoming requests. |
+| `path` | `string` | "glob-string" that matches on the request path. If not provided then it will match on all incoming requests. |
+| `precedence` | `int` | Allows forcing a precedence ordering on the rules. By default the rules are evaluated in the order they are in the `FilterPolicy.spec.rules` field. However, multiple FilterPolicy's can be applied to a cluster. To ensure that a specific ordering is enforced then using a precedence is an option. |
+| `filters` | \[\][FilterReference][] | List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths. |
+
+**Note:** The wildcard `*` is supported for both `path` and `host`.
+
+When multiple Filters are specified in a rule:
+
+- The filters are gone through in order
+- Each filter may either:
+ - return a direct HTTP *response*, intended to be sent back to the requesting HTTP client (normally *denying* the request from being forwarded to the upstream service) OR
+ - return a modification to make to the HTTP *request* before sending it to other filters or the upstream service (normally *allowing* the request to be forwarded to the upstream service with modifications).
+- If a filter has an `ifRequestHeader` setting, the filter is skipped
+ unless the request (including any modifications made by earlier
+ filters) has the HTTP header field `name`
+ set to (or not set to if `negate: true`):
+ - a non-empty string if neither `value` nor `valueRegex` are set
+ - the exact string `value` (case-sensitive) (if `value` is set)
+ - a string that matches the regular expression `valueRegex` (if
+ `valueRegex` is set). This uses [RE2][] syntax (always, not
+ obeying [`regex_type`][] in the Ambassador module) but does not
+ support the `\C` escape sequence.
+- Modifications to the request are cumulative; later filters have access to _all_ headers inserted by earlier filters.
+
+### FilterReference
+
+A refernce to a filter to be executed when an incoming request matches the `FilterPolicy` Rule
+
+| **Field** | **Type** | **Description** |
+|-------------------|-----------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name that identifies the Filter |
+| `namespace` | `string` | Kubernetes namespace that the Filter resides. It must be a RFC 1123 label. Valid values include: `"example"`, Invalid values include: `"example.com"` (`.` is an invalid character). This validation is based off of the [corresponding Kubernetes validation]. |
+| `onDeny` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `onAllow` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter allows the request. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not. |
+| `arguments` | [FilterArguments][] | Untyped map that allows for additional configuration specific to each filter to be provided |
+
+**onDeny Options**:
+
+- `"break"`: End processing, and return the response directly to
+ the requesting HTTP client. Later filters are not called. The request is not forwarded to the upstream service.
+- `"continue"`: Continue processing. The request is passed to the
+ next filter listed; or if at the end of the list, it is forwarded to the upstream service. The HTTP response returned from the filter is discarded.
+
+**onAllow Options**:
+
+- `"break"`: Apply the modification to the request, then end filter processing, and forward the modified request to the upstream service. Later filters are not called.
+- `"continue"`: Continue processing. Apply the request modification, then pass the modified request to the next filter
+ listed; or if at the end of the list, forward it to the upstream service.
+
+### HTTPHeaderMatch
+
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of the HTTP Header to be matched. Only one of `value` or `valueRegex` can be configured |
+| `valueRegex` | `string` | Regex expression for matching the value of the HTTP Header. Only one of `value` or `valueRegex` can be configured |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### FilterArguments
+
+The Filter arguments fiels is an untyped map that allows for additional configuration specific to each filter to be provided.
+Refer to the usage guides for each filter type to see if it has any arguments that can be supplied.
+
+[FilterPolicyRule]: #filterpolicyrule
+[FilterReference]: #filterreference
+[FilterArguments]: #filterarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[Filter custom resource]: ../filter
+[the v1alpha1 FilterPolicy api reference]: ../../../gateway-getambassador/v1alpha1/filterpolicy
+[corresponding Kubernetes validation]: https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
diff --git a/docs/edge-stack/3.9/doc-links.yml b/docs/edge-stack/3.9/doc-links.yml
new file mode 100644
index 000000000..6d32f7e88
--- /dev/null
+++ b/docs/edge-stack/3.9/doc-links.yml
@@ -0,0 +1,327 @@
+ - title: Quick start
+ link: /tutorials/getting-started
+ - title: Core concepts
+ items:
+ - title: Kubernetes network architecture
+ link: /topics/concepts/kubernetes-network-architecture
+ - title: 'The Ambassador operating model: GitOps and continuous delivery'
+ link: /topics/concepts/gitops-continuous-delivery
+ - title: Progressive delivery
+ link: /topics/concepts/progressive-delivery
+ - title: Microservices API gateways
+ link: /topics/concepts/microservices-api-gateways
+ - title: $productName$ architecture
+ link: /topics/concepts/architecture
+ - title: Rate limiting at the edge
+ link: /topics/concepts/rate-limiting-at-the-edge
+ - title: Installation and updates
+ link: /topics/install/
+ items:
+ - title: Install with Helm
+ link: /topics/install/helm
+ - title: Install with Kubernetes YAML
+ link: /topics/install/yaml-install
+ - title: Try the demo with Docker
+ link: /topics/install/docker
+ - title: Upgrade or migrate to a newer version
+ link: /topics/install/migration-matrix
+ - title: Edge Stack user guide
+ items:
+ - title: Deployment
+ items:
+ - title: Deployment architecture
+ link: /topics/running/ambassador-deployment
+ - title: $productName$ environment variables and ports
+ link: /topics/running/environment
+ - title: $productName$ and Redis
+ link: /topics/running/aes-redis
+ - title: $productName$ with AWS
+ link: /topics/running/ambassador-with-aws
+ - title: $productName$ with GKE
+ link: /topics/running/ambassador-with-gke
+ - title: Advanced deployment configuration
+ link: /topics/running/running
+ - title: Performance and scaling $productName$
+ link: /topics/running/scaling
+ - title: Active health checking configuration
+ link: /howtos/active-health-checking
+ - title: HTTP/3 configuration
+ items:
+ - title: HTTP3 setup in $productName$
+ link: /topics/running/http3
+ - title: HTTP/3 with AKS
+ link: /howtos/http3-aks
+ - title: HTTP/3 with EKS
+ link: /howtos/http3-eks
+ - title: HTTP/3 with GKE
+ link: /howtos/http3-gke
+ - title: Web Application Firewalls
+ items:
+ - title: $productName$'s Web Application Firewall
+ link: /howtos/web-application-firewalls
+ - title: Configuring Web Application Firewall rules
+ link: /howtos/web-application-firewalls-config
+ - title: Using Web Application Firewalls in Production
+ link: /howtos/web-application-firewalls-in-production
+ - title: Service routing and communication
+ items:
+ - title: Configuring $productName$ to communicate
+ link: /howtos/configure-communications
+ - title: Get traffic from the edge
+ link: /howtos/route
+ - title: TCP connections
+ link: /topics/using/tcpmappings
+ - title: gRPC connections
+ link: /howtos/grpc
+ - title: WebSocket connections
+ link: /howtos/websockets
+ - title: Authentication
+ items:
+ - title: Basic authentication
+ link: /howtos/ext-filters
+ - title: Using the OAuth2 filter for SSO
+ link: /howtos/oauth-oidc-auth
+ - title: Single Sign-On with Google
+ link: /howtos/sso/google
+ - title: Single Sign-On with Keycloak
+ link: /howtos/sso/keycloak
+ - title: Kubernetes SSO with OIDC and Keycloak
+ link: /howtos/auth-kubectl-keycloak
+ - title: Single Sign-On with Okta
+ link: /howtos/sso/okta
+ - title: Single Sign-On with Auth0
+ link: /howtos/sso/auth0
+ - title: Single Sign-On with Azure AD
+ link: /howtos/sso/azure
+ - title: Single Sign-On with OneLogin
+ link: /howtos/sso/onelogin
+ - title: Single Sign-On with Salesforce
+ link: /howtos/sso/salesforce
+ - title: Single Sign-On with UAA
+ link: /howtos/sso/uaa
+ - title: Authentication extension
+ link: /topics/running/aes-extensions/authentication
+ - title: Rate limiting
+ items:
+ - title: Rate limiting in $productName$
+ link: /howtos/advanced-rate-limiting
+ - title: Basic rate limiting
+ link: /topics/using/rate-limits/
+ - title: Rate limiting on token claims
+ link: /howtos/token-ratelimit
+ - title: Rate limiting reference
+ link: /topics/using/rate-limits/rate-limits
+ - title: Rate limiting extension
+ link: /topics/running/aes-extensions/ratelimit
+ - title: Service monitoring
+ items:
+ - title: Explore distributed tracing and Kubernetes monitoring
+ link: /howtos/dist-tracing
+ - title: Distributed tracing with Datadog
+ link: /howtos/tracing-datadog
+ - title: Distributed tracing with Zipkin
+ link: /howtos/tracing-zipkin
+ - title: Distributed tracing with LightStep
+ link: /howtos/tracing-lightstep
+ - title: Monitoring with Prometheus and Grafana
+ link: /howtos/prometheus
+ - title: Statistics
+ link: /topics/running/statistics
+ - title: Envoy statistics with StatsD
+ link: /topics/running/statistics/envoy-statsd
+ - title: The metrics endpoint
+ link: /topics/running/statistics/8877-metrics
+ - title: $productName$ integrations
+ items:
+ - title: Knative Serverless Framework
+ link: /howtos/knative
+ - title: ExternalDNS integration
+ link: /howtos/external-dns
+ - title: Consul integration
+ link: /howtos/consul
+ - title: Istio integration
+ link: /howtos/istio
+ - title: Linkerd 2 integration
+ link: /howtos/linkerd2
+ - title: Technical reference
+ items:
+ - title: Using Custom Resources
+ items:
+ - title: The Host resource
+ link: /topics/running/host-crd
+ - title: The Listener resource
+ link: /topics/running/listener
+ - title: The Module resource
+ link: /topics/running/ambassador
+ - title: The Mapping resource
+ link: /topics/using/intro-mappings
+ - title: Advanced Mapping configuration
+ link: /topics/using/mappings
+ - title: TLS configuration
+ items:
+ - title: TLS overview
+ link: /topics/running/tls/
+ - title: Cleartext support
+ link: /topics/running/tls/cleartext-redirection
+ - title: Mutual TLS (mTLS)
+ link: /topics/running/tls/mtls
+ - title: Server Name Indication (SNI)
+ link: /topics/running/tls/sni
+ - title: TLS origination
+ link: /topics/running/tls/origination
+ - title: TLS termination and enabling HTTPS
+ link: /howtos/tls-termination
+ - title: Using cert-manager
+ link: /howtos/cert-manager
+ - title: Client certificate validation
+ link: /howtos/client-cert-validation
+ - title: Filters
+ items:
+ - title: Using Filters and FilterPolicies
+ link: /topics/using/filters/
+ - title: Using OAuth2 Filters
+ link: /topics/using/filters/oauth2
+ - title: Using JWT Filters
+ link: /topics/using/filters/jwt
+ - title: Using External Filters
+ link: /topics/using/filters/external
+ - title: Using Plugin Filters
+ link: /topics/using/filters/plugin
+ - title: Using API Keys Filter
+ link: /topics/using/filters/apikeys
+ - title: Ingress and load balancing
+ items:
+ - title: AuthService settings
+ link: /topics/using/authservice
+ - title: Automatic retries
+ link: /topics/using/retries
+ - title: Canary releases
+ link: /topics/using/canary
+ - title: Circuit Breakers
+ link: /topics/using/circuit-breakers
+ - title: Cross-Origin Resource Sharing (CORS)
+ link: /topics/using/cors
+ - title: Ingress controller
+ link: /topics/running/ingress-controller
+ - title: Load balancing
+ link: /topics/running/load-balancer
+ - title: Service discovery and resolvers
+ link: /topics/running/resolvers
+ - title: Headers
+ items:
+ - title: Headers overview
+ link: /topics/using/headers/headers
+ - title: Add request headers
+ link: /topics/using/headers/add_request_headers
+ - title: Remove request headers
+ link: /topics/using/headers/remove_request_headers
+ - title: Add response headers
+ link: /topics/using/headers/add_response_headers
+ - title: Remove response headers
+ link: /topics/using/headers/remove_response_headers
+ - title: Header-based routing
+ link: /topics/using/headers/headers
+ - title: Host header
+ link: /topics/using/headers/host
+ - title: Routing
+ items:
+ - title: Keepalive
+ link: /topics/using/keepalive
+ - title: Method-based routing
+ link: /topics/using/method
+ - title: Prefix regex
+ link: /topics/using/prefix_regex
+ - title: Query parameter-based routing
+ link: /topics/using/query_parameters/
+ - title: Redirects
+ link: /topics/using/redirects
+ - title: Rewrites
+ link: /topics/using/rewrites
+ - title: Timeouts
+ link: /topics/using/timeouts
+ - title: Traffic shadowing
+ link: /topics/using/shadowing
+ - title: Plug-in services
+ items:
+ - title: Authentication service
+ link: /topics/running/services/auth-service
+ - title: ExtAuth protocol
+ link: /topics/running/services/ext_authz
+ - title: Log service
+ link: /topics/running/services/log-service
+ - title: Rate limit service
+ link: /topics/running/services/rate-limit-service
+ - title: Tracing service
+ link: /topics/running/services/tracing-service
+ - title: Traffic management
+ items:
+ - title: Custom error responses
+ link: /topics/running/custom-error-responses
+ - title: Gzip compression
+ link: /topics/running/gzip
+ - title: API
+ items:
+ - title: Gateway API
+ link: /topics/using/gateway-api
+ - title: Developer Portal
+ link: /topics/using/dev-portal
+ - title: CRD API References
+ items:
+ - title: getambassador.io/v3alpha1
+ items:
+ - title: Filter
+ link: /custom-resources/getambassador/v3alpha1/filter
+ items:
+ - title: The OAuth2 Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-oauth2
+ - title: The JWT Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-jwt
+ - title: The External Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-external
+ - title: The APIKey Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-apikey
+ - title: The Plugin Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-plugin
+ - title: FilterPolicy
+ link: /custom-resources/getambassador/v3alpha1/filterpolicy
+ - title: gateway.getambassador.io/v1alpha1
+ items:
+ - title: Filter
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter
+ items:
+ - title: The OAuth2 Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-oauth2
+ - title: The JWT Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-jwt
+ - title: The External Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-external
+ - title: The APIKey Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-apikey
+ - title: The Plugin Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-plugin
+ - title: FilterPolicy
+ link: /custom-resources/gateway-getambassador/v1alpha1/filterpolicy
+ - title: WebApplicationFirewall
+ link: /custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall
+ - title: WebApplicationFirewallPolicy
+ link: /custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy
+ - title: FAQs
+ link: /about/faq
+ - title: Troubleshooting
+ link: /topics/running/debugging
+ - title: Known issues
+ link: /about/known-issues
+ - title: Changes in $productName$ 2.X
+ link: /about/changes-2.x
+ - title: Changes in $productName$ 3.X
+ link: /about/changes-3.y
+ - title: Release Notes
+ link: /release-notes
+ - title: Community
+ link: /community
+ - title: End of Life Policy
+ link: /about/aes-emissary-eol
+ - title: $productName$ Licenses
+ link: topics/using/licenses
+ - title: Open Source Dependency Licenses
+ link: licenses
diff --git a/docs/edge-stack/3.9/howtos/advanced-rate-limiting.md b/docs/edge-stack/3.9/howtos/advanced-rate-limiting.md
new file mode 100644
index 000000000..491b71cd0
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/advanced-rate-limiting.md
@@ -0,0 +1,246 @@
+# Advanced rate limiting
+
+$productName$ features a built-in [Rate Limit Service (RLS)](../../topics/running/services/rate-limit-service/#external-rate-limit-service). The $productName$ RLS uses a decentralized configuration model that enables individual teams the ability to independently manage [rate limits](https://www.getambassador.io/kubernetes-glossary/rate-limiting) independently.
+
+All of the examples on this page use the backend service of the quote sample application to illustrate how to perform the rate limiting functions.
+
+## Rate Limiting in $productName$
+
+In $productName$, the `RateLimit` resource defines the policy for rate limiting. The rate limit policy is applied to individual requests according to the labels you add to the `Mapping` resource. This allows you to assign labels based on the particular needs of you rate limiting policies and apply the `RateLimit` policies to only the domains in the related `Mapping` resource.
+
+You can apply the `RateLimit` policy globally to all requests with matching labels from the `Module` resource. This can be used in conjunction with the `Mapping` resource to have a global rate limit with more granular rate limiting for specific requests that go through that specific `Mapping` resource.
+
+ In order for you to enact rate limiting policies:
+
+* Each domain you target needs to have labels.
+* For individual request, the service's `Mapping` resource needs to contain the labels related to the domains you want to apply the rate limiting policy to.
+* For global requests, the service's `Module` resource needs to contain the labels related to the policy you want to apply.
+* The `RateLimit` resource needs to set the rate limit policy for the labels the `Mapping` resource.
+
+
+## Rate limiting for availability
+
+Global rate limiting applies to the entire Kubernetes service mesh. This example shows how to limit the `quote` service to 3 requests per minute.
+
+1. First, add a request label to the `request_label_group` of the `quote` service's `Mapping` resource. This example uses `backend` for the label:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - generic_key:
+ value: backend
+ ```
+
+ Apply the mapping configuration changes with `kubectl apply -f quote-backend.yaml`.
+
+
+ You need to use v2 or later for the apiVersion in the Mapping resource. Previous versions do not support labels.
+
+
+2. Next, configure the `RateLimit` resource for the service. Create a new YAML file named `backend-ratelimit.yaml` and apply the rate limit details as follows:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{generic_key: backend}]
+ rate: 3
+ unit: minute
+ ```
+
+ In the code above, the `generic_key` is a hard-coded value that is used when you add a single string label to a request.
+
+3. Deploy the rate limit with `kubectl apply -f backend-ratelimit.yaml`.
+
+## Per user rate limiting
+
+Per user rate limiting enables you to apply the defined rate limit to specific IP addresses. To allow per user rate limits, you need to make sure you've properly configured $productName$ to [propagate your original client IP address](../../topics/running/ambassador/#trust-downstream-client-ip).
+
+This example shows how to use the `remote_address` special value in the mapping to target specific IP addresses:
+
+1. Add a request label to the `request_label_group` of the `quote` service's `Mapping` resource. This example uses `remote_address` for the label:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - remote_address:
+ key: remote_address
+ ```
+
+2. Update the rate limit amounts for the `RateLimit` service and enter the `remote_address` to the following pattern:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}]
+ rate: 3
+ unit: minute
+ ```
+
+## Load shedding
+
+Another technique for rate limiting involves load shedding. With load shedding, you can define which HTTP request method to allow or deny.
+
+This example shows how to implement load per user rate limiting along with load shedding on `GET` requests.
+To allow per user rate limits, you need to make sure you've properly configured $productName$ to [propagate your original client IP address](../../topics/running/ambassador#trust-downstream-client-ip).
+
+1. Add a request labels to the `request_label_group` of the `quote` service's `Mapping` resource. This example uses `remote_address` for the per user limit, and `backend_http_method`for load shedding. The load shedding uses `":method"` to identify that the `RateLimit` will use a HTTP request method in its pattern.
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - remote_address:
+ key: remote_address
+ - request_headers:
+ key: backend_http_method
+ header_name: ":method"
+ ```
+
+2. Update the rate limit amounts for the `RateLimit` service.
+For the rate limit `pattern`, include the `remote_address` IP address and the `backend_http_mthod`.
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}, {backend_http_method: GET}]
+ rate: 3
+ unit: minute
+ ```
+
+ When a pattern has multiple criteria, the rate limit runs when when any of the rules of the pattern match. For the example above, this means either a `remote_address` or `backend_http_method` pattern triggers the rate limiting.
+
+## Global rate limiting
+
+Similar to the per user rate limiting, you can use [global rate limiting](../../topics/using/rate-limits) to assign a rate limit to any unique IP addresses call to your service. Unlike the previous examples, you need to add your labels to the `Module` resource rather than the `Mapping` resource. This is because the `Module` resource applies the labels to all the requests in $productName$, whereas the labels in `Mapping` only apply to the requests that use that `Mapping` resource.
+
+1. Add a request label to the `request_label_group` of the `quote` service's `Module` resource. This example uses the `remote_address` special value.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Module
+ metadata:
+ name: ambassador
+ spec:
+ config:
+ use_remote_address: true
+ default_label_domain: ambassador
+ default_labels:
+ ambassador:
+ defaults:
+ - remote_address:
+ key: remote_address
+ ```
+2. Update the rate limit amounts for the `RateLimit` service and enter the `remote_address` to the following pattern:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: global-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}]
+ rate: 10
+ unit: minute
+ ```
+
+### Bypassing a global rate limit
+
+Sometimes, you may have an API that cannot handle as much load as others in your cluster. In this case, a global rate limit may not be enough to ensure this API is not overloaded with requests from a user. To protect this API, you can create a label that tells $productName$ to apply a stricter limit on requests.
+In the example above, the global rate limit is defined in the `Module` resource. This applies the limit to all requests. In conjunction with the global limit defined in the `Module` resource, you can add more granular rate limiting to a `Mapping` resource, which will only apply to requests that use that 'Mapping'.
+
+1. In addition to the configurations applied in the global rate limit example above, add an additional label to the `request_label_group` of the `Mapping` resource. This example uses `backend` for the label:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - generic_key:
+ value: backend
+ ```
+
+2. Now, the `request_label_group` contains both the `generic_key: backend` and the `remote_address` key applied from the global rate limit. This creates a separate `RateLimit` object for this route:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}, {generic_key: backend}]
+ rate: 3
+ unit: minute
+ ```
+
+ Requests to `/backend/` now are now limited after 3 requests. All other requests use the global rate limit policy.
+
+## Rate limit matching rules
+
+The following rules apply to the rate limit patterns:
+
+* Patterns are order-sensitive and must be entered in the same order in which a request is labeled.
+* Every label in a label group must exist in the pattern in order for matching to occur.
+* By default, any type of failure lets the request pass through (fail open).
+* $productName$ sets a hard timeout of 20ms on the rate limiting service. If the rate limit service does not respond within the timeout period, the request passes through.
+* If a pattern does not match, the request passes through.
+
+## Troubleshooting rate limiting
+
+The most common source of failure of the rate limiting service occurs when the labels generated by $productName$ do not match the rate limiting pattern. By default, the rate limiting service logs all incoming labels from $productName$. Use a tool such as [Stern](https://github.com/stern/stern) to watch the rate limiting logs from $productName$ and ensure the labels match your descriptor.
+
+## More
+
+For more on rate limiting, see the [rate limit guide](../../topics/using/rate-limits/).
diff --git a/docs/edge-stack/3.9/howtos/auth-kubectl-keycloak.md b/docs/edge-stack/3.9/howtos/auth-kubectl-keycloak.md
new file mode 100644
index 000000000..04996fd35
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/auth-kubectl-keycloak.md
@@ -0,0 +1,294 @@
+# Kubernetes SSO with OIDC and Keycloak
+
+Developers use `kubectl` to access Kubernetes clusters. By default `kubectl` uses a certificate to authenticate to the Kubernetes API. This means that when multiple developers need to access a cluster, the certificate needs to be shared. Sharing the credentials to access a Kubernetes cluster presents a significant security problem. Compromise of the certificate is very easy and the consequences can be catastrophic.
+
+In this tutorial, we walk through how to set up your Kubernetes cluster to add Single Sign-On support for `kubectl` using OpenID Connect (OIDC) and Keycloak. Instead of using a shared certificate, users will be able to use their own personal credentials to use `kubectl` with `kubelogin`.
+
+## Prerequisites
+
+This tutorial relies on $AESproductName$ to manage access to your Kubernetes cluster, and uses Keycloak as your identity provider. To get started:
+
+*Note* This guide was designed and validated using an Azure AKS Cluster. It's possible that this procedure will work with other cloud providers, but there is a lot of variance in the Authentication mechanisms for the Kubernetes API. See the troubleshooting note at the bottom for more info.
+
+* Azure AKS Cluster [here](https://docs.microsoft.com/en-us/azure/aks/tutorial-kubernetes-deploy-cluster)
+* Install $AESproductName$ [here](../../topics/install/)
+* Deploy Keycloak on Kubernetes [here](https://www.keycloak.org/getting-started/getting-started-kube)
+
+## Cluster Setup
+
+In this section, we'll configure your Kubernetes cluster for single-sign on.
+
+### 1. Authenticate $AESproductName$ with Kubernetes API
+
+1. Delete the openapi mapping from the Ambassador namespace `kubectl delete -n ambassador ambassador-devportal-api`. (this mapping can conflict with `kubectl` commands)
+
+2. Create a new private key using `openssl genrsa -out aes-key.pem 4096`.
+
+3. Create a file `aes-csr.cnf` and paste the following config.
+
+ ```cnf
+ [ req ]
+ default_bits = 2048
+ prompt = no
+ default_md = sha256
+ distinguished_name = dn
+
+ [ dn ]
+ CN = ambassador-kubeapi # Required
+
+ [ v3_ext ]
+ authorityKeyIdentifier=keyid,issuer:always
+ basicConstraints=CA:FALSE
+ keyUsage=keyEncipherment,dataEncipherment
+ extendedKeyUsage=serverAuth,clientAuth
+ ```
+
+4. Create a certificate signing request with the config file we just created. `openssl req -config ./aes-csr.cnf -new -key aes-key.pem -nodes -out aes-csr.csr`.
+
+5. Create and apply the following YAML for a CertificateSigningRequest. Replace {{BASE64_CSR}} with the value from `cat aes-csr.csr | base64`. Note that this is `aes-csr.csr`, and not `aes-csr.cnf`.
+
+ ```yaml
+ apiVersion: certificates.k8s.io/v1beta1
+ kind: CertificateSigningRequest
+ metadata:
+ name: aes-csr
+ spec:
+ groups:
+ - system:authenticated
+ request: {{BASE64_CSR}} # Base64 encoded aes-csr.csr
+ usages:
+ - digital signature
+ - key encipherment
+ - server auth
+ - client auth
+ ```
+
+6. Check csr was created: `kubectl get csr` (it will be in pending state). After confirmation, run `kubectl certificate approve aes-csr`. You can check `kubectl get csr` again to see that it's in the `Approved, Issued` state.
+
+7. Get the resulting certificate and put it into a pem file. `kubectl get csr aes-csr -o jsonpath='{.status.certificate}' | base64 -d > aes-cert.pem`.
+
+8. Create a TLS `Secret` using our private key and public certificate. `kubectl create secret tls -n ambassador aes-kubeapi --cert ./aes-cert.pem --key ./aes-key.pem`
+
+9. Create a `Mapping` and `TLSContext` for the Kube API.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: TLSContext
+ metadata:
+ name: aes-kubeapi-context
+ namespace: ambassador
+ spec:
+ hosts:
+ - "*"
+ secret: aes-kubeapi
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: aes-kubeapi-mapping
+ namespace: ambassador
+ spec:
+ hostname: "*"
+ prefix: /
+ allow_upgrade:
+ - spdy/3.1
+ service: https://kubernetes.default.svc
+ timeout_ms: 0
+ tls: aes-kubeapi-context
+ ```
+
+10. Create RBAC for the "aes-kubeapi" user by applying the following YAML.
+
+ ```yaml
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: aes-impersonator-role
+ rules:
+ - apiGroups: [""]
+ resources: ["users", "groups", "serviceaccounts"]
+ verbs: ["impersonate"]
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ name: aes-impersonator-rolebinding
+ subjects:
+ - apiGroup: rbac.authorization.k8s.io
+ kind: User
+ name: aes-kubeapi
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: aes-impersonator-role
+ ```
+
+As a quick check, you should be able to `curl https:///api` and get a response similar to the following:
+
+ ```json
+ {
+ "kind": "APIVersions",
+ "versions": [
+ "v1"
+ ],
+ "serverAddressByClientCIDRs": [
+ {
+ "clientCIDR": "0.0.0.0/0",
+ "serverAddress": "\"\":443"
+ }
+ ]
+ }%
+ ```
+
+### 2. Set up Keycloak config
+
+1. Create a new Realm and Client (e.g. ambassador, ambassador)
+2. Make sure that `http://localhost:8000` and `http://localhost:18000` are valid Redirect URIs
+3. Set access type to confidential and Save
+4. Go to the Credentials tab and note down the secret
+5. Go to the user tab and create a user with the first name "john"
+
+### 3. Create a ClusterRole and ClusterRoleBinding for the OIDC user "john"
+
+1. Add the following RBAC to create a user "john" that only allowed to perform `kubectl get services` in the cluster.
+
+ ```yaml
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ name: john-binding
+ subjects:
+ - kind: User
+ name: john
+ apiGroup: rbac.authorization.k8s.io
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: john-role
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: john-role
+ rules:
+ - apiGroups: [""]
+ resources: ["services"]
+ verbs: ["get", "list"]
+ ```
+
+2. Test the API again with the following 2 `curls`: `curl https:///api/v1/namespaces/default/services?limit=500 -H "Impersonate-User: "john"` and `curl https:///api/v1/namespaces/default/pods?limit=500 -H "Impersonate-User: "john"`. You will find that the first curl should succeeds and the second curl should fail with the following response.
+
+```json
+{
+ "kind": "Status",
+ "apiVersion": "v1",
+ "metadata": {
+
+ },
+ "status": "Failure",
+ "message": "pods is forbidden: User \"john\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
+ "reason": "Forbidden",
+ "details": {
+ "kind": "pods"
+ },
+ "code": 403
+}
+```
+
+### 4. Create a JWT filter to authenticate the user
+
+1. Create the following JWT `Filter` and `FilterPolicy` based on this template:
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: "kubeapi-jwt-filter"
+ namespace: "ambassador"
+ spec:
+ JWT:
+ jwksURI: https:///auth/realms//protocol/openid-connect/certs # If the keycloak instance is internal, you may want to use the internal k8s endpoint (e.g. http://keycloak.keycloak) instead of figuring out how to exclude JWKS requests from the FilterPolicy
+ injectRequestHeaders:
+ - name: "Impersonate-User" # Impersonate-User is mandatory, you can also add an Impersonate-Groups if you want to do group-based RBAC
+ value: "{{ .token.Claims.given_name }}" # This uses the first name we specified in the Keycloak user account
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: "kubeapi-filter-policy"
+ namespace: "ambassador"
+ spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: kubeapi-jwt-filter
+ ```
+
+## Client set up
+
+Now, we need to set up the client. Each user who needs to access the Kubernetes cluster will need to follow these steps.
+
+### 1. Install kubelogin
+
+1. Install [kubelogin](https://github.com/int128/kubelogin#getting-started). Kubelogin is a `kubectl` plugin that enables OpenID Connect login with `kubectl`.
+
+2. Edit your local Kubernetes config file (either `~/.kube/config`, or your `$KUBECONFIG` file) to include the following, making sure to replace the templated values.
+
+ ```yaml
+ apiVersion: v1
+ kind: Config
+ clusters:
+ - name: azure-ambassador
+ cluster:
+ server: https://
+ contexts:
+ - name: azure-ambassador-kube-api
+ context:
+ cluster: azure-ambassador
+ user: azure-ambassador
+ users:
+ - name: azure-ambassador
+ user:
+ exec:
+ apiVersion: client.authentication.k8s.io/v1beta1
+ command: kubectl
+ args:
+ - oidc-login
+ - get-token
+ - --oidc-issuer-url=https:///auth/realms/
+ - --oidc-client-id=
+ - --oidc-client-secret=
+ ```
+
+3. Switch to the context set above (in the example it's `azure-ambassador-kube-api`).
+
+4. Run `kubectl get svc`. This should open a browser page to the Keycloak login. Type in the credentials for "john" and, on success, return to the terminal to see the kubectl response. Congratulations, you've set up Single Sign-On with Kubernetes!
+
+5. Now try running `kubectl get pods`, and notice we get an `Error from server (Forbidden): pods is forbidden: User "john" cannot list resource "pods" in API group "" in the namespace "default"`. This is expected because we explicitly set up "john" to only have access to view `Service` resources, and not `Pods`.
+
+### 7. Logging Out
+
+1. Delete the token cache with `rm -r ~/.kube/cache/oidc-login`
+2. You may also have to remove session cookies in your browser or do a remote logout in the keycloak admin page.
+
+### Troubleshooting
+
+1. Why isn't this process working in my `` cluster?
+ Authentication to the Kubernetes API is highly cluster specific. Many use x509 certificates, but as a notable exception, Amazon's Elastic Kubernetes Service, for example, uses an Authenticating Webhook that connects to their IAM solution for Authentication, and so is not compatible specifically with this guide.
+2. What if I want to use RBAC Groups?
+ User impersonation allows you to specify a Group using the `Impersonate-Group` header. As such, if you wanted to use any kind of custom claims for the ID token, they can be mapped to the `Impersonate-Group` header. Note that you always have to use an `Impersonate-Name` header, even if you're relying solely on the Group for Authorization.
+3. I keep getting a 401 `Failure`, `Unauthorized` message, even for `https:///api`.
+ This likely means that there is either something wrong with the Certificate that was issued, or there's something wrong with your `TLSContext` or `Mapping` config. $AESproductName$ must present the correct certificate to the Kubernetes API and the RBAC usernames and the CN of the certificate have to be consistent with one another.
+4. Do I have to use `kubelogin`?
+ Technically no. Any method of obtaining an ID or Access token from an Identity Provider will work. You can then pass the token using `--token ` when running `kubectl`. `kubelogin` simply automates the process of getting the ID token and attaching it to a `kubectl` request.
+
+## Under the Hood
+
+In this tutorial, we set up $AESproductName$ to [impersonate a user](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation) to access the Kubernetes API. Requests get sent to $AESproductName$, which functions as an Authenticating Proxy. $AESproductName$ uses its integrated authentication mechanism to authenticate the external request's identity and sets the User and Group based on Claims recieved by the `Filter`.
+
+The general flow of the `kubectl` command is as follows: On making an unauthenticated kubectl command, `kubelogin` does a browser open/redirect in order to do OIDC token negotiation. `kubelogin` obtains an OIDC Identity Token (notice this is not an access token) and sends it to $AESproductName$ in an Authorization header. $AESproductName$ validates the Identity Token and parses Claims from it to put into `Impersonate-XXX` headers. $AESproductName$ then scrubs the Authorization header and replaces it with the Admin token we set up in step 1. $AESproductName$ then forwards this request with the new Authorization and Impersonate headers to the KubeAPI to first Authenticate, and then Authorize based on Kubernetes RBAC.
diff --git a/docs/edge-stack/3.9/howtos/controlling-404.md b/docs/edge-stack/3.9/howtos/controlling-404.md
new file mode 100644
index 000000000..5d8357fc5
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/controlling-404.md
@@ -0,0 +1,23 @@
+# Controlling the Edge Stack 404 Page
+
+Established users will want to better control 404 behavior both for usability and
+security. You can leverage the `Mapping` resource to implement this
+functionality to your cluster. $productName$ users can use a 'catch-all' mapping
+using the `/` prefix in a `Mapping` configuration. The simplest `Mapping`, described
+below, returns only 404 text. To use a custom 404 landing page, simply insert your
+service and remove the rewrite value.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: "404-fallback"
+spec:
+ hostname: "*"
+ prefix: "/"
+ rewrite: "/404/" # This must not map to any existing prefix!
+ service: localhost:8500 # This needs to exist, but _not_ respond on /404/
+```
+
+For more information on the `Mapping` resource, see
+[Advanced `Mapping` Configuration](../../topics/using/mappings).
diff --git a/docs/edge-stack/3.9/howtos/ext-filters.md b/docs/edge-stack/3.9/howtos/ext-filters.md
new file mode 100644
index 000000000..f7e5edc47
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/ext-filters.md
@@ -0,0 +1,208 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Basic authentication
+
+
+ This guide applies to $AESproductName$, use of this guide on the $OSSproductName$ is not recommended. API Gateway does authentication using the AuthService resource instead of the Filter resource as described below.
+
+
+$AESproductName$ can authenticate incoming requests before routing them to a backing
+service. In this tutorial, we'll configure $AESproductName$ to use an external third
+party authentication service. We're assuming also that you are running the
+quote application in your cluster as described in the
+[$AESproductName$ tutorial](../../tutorials/quickstart-demo/).
+
+## 1. Deploy the authentication service
+
+$AESproductName$ delegates the actual authentication logic to a third party authentication service. We've written a [simple authentication service](https://github.com/datawire/ambassador-auth-service) that:
+
+- listens for requests on port 3000;
+- expects all URLs to begin with `/extauth/`;
+- performs HTTP Basic Auth for all URLs starting with `/backend/get-quote/` (other URLs are always permitted);
+- accepts only user `username`, password `password`; and
+- makes sure that the `x-qotm-session` header is present, generating a new one if needed.
+
+$AESproductName$ routes _all_ requests through the authentication service: it relies on the auth service to distinguish between requests that need authentication and those that do not. If $AESproductName$ cannot contact the auth service, it will return a 503 for the request; as such, **it is very important to have the auth service running before configuring $AESproductName$ to use it.**
+
+Here's the YAML we'll start with:
+
+```yaml
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: example-auth
+spec:
+ type: ClusterIP
+ selector:
+ app: example-auth
+ ports:
+ - port: 3000
+ name: http-example-auth
+ targetPort: http-api
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: example-auth
+spec:
+ replicas: 1
+ strategy:
+ type: RollingUpdate
+ selector:
+ matchLabels:
+ app: example-auth
+ template:
+ metadata:
+ labels:
+ app: example-auth
+ spec:
+ containers:
+ - name: example-auth
+ image: docker.io/datawire/ambassador-auth-service:2.0.0
+ imagePullPolicy: Always
+ ports:
+ - name: http-api
+ containerPort: 3000
+ resources:
+ limits:
+ cpu: "0.1"
+ memory: 100Mi
+```
+
+Note that the cluster does not yet contain any $AESproductName$ AuthService definition. This is intentional: we want the service running before we tell $AESproductName$ about it.
+
+The YAML above is published at getambassador.io, so if you like, you can just do
+
+```
+kubectl apply -f https://app.getambassador.io/yaml/v2-docs/$ossVersion$/demo/demo-auth.yaml
+```
+
+to spin everything up. (Of course, you can also use a local file, if you prefer.)
+
+Wait for the pod to be running before continuing. The output of `kubectl get pods` should look something like
+
+```
+$ kubectl get pods
+NAME READY STATUS RESTARTS AGE
+example-auth-6c5855b98d-24clp 1/1 Running 0 4m
+```
+Note that the `READY` field says `1/1` which means the pod is up and running.
+
+## 2. Configure $AESproductName$ authentication
+
+Once the auth service is running, we need to tell $AESproductName$ about it. The easiest way to do that is to first map the `example-auth` service with the following `Filter`:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: authentication
+spec:
+ External:
+ auth_service: "example-auth:3000"
+ path_prefix: "/extauth"
+ allowed_request_headers:
+ - "x-qotm-session"
+ allowed_authorization_headers:
+ - "x-qotm-session"
+```
+
+This configuration tells $AESproductName$ about the `Filter`, notably that it needs the `/extauth` prefix, and that it's OK for it to pass back the `x-qotm-session` header. Note that `path_prefix` and `allowed_headers` are optional.
+
+Next you must apply the `Filter` to your desired hosts and paths using a `FilterPolicy`. The following would enable your `Filter` on requests to all hosts and paths (just remember that our authentication service is only configured to perform authentication on requests to `/backend/get-quote/`, see the [auth service's repo](https://github.com/datawire/ambassador-auth-service) for more information).
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: authentication
+spec:
+ rules:
+ - host: "*"
+ path: /*
+ filters:
+ - name: authentication
+```
+
+You can also apply the `Filter` only to specific hosts and/or paths, allowing you to only require authentication on certain routes. The following `FilterPolicy` would only run your `Filter` to requests to the `/backend/get-quote/` path:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: authentication
+spec:
+ rules:
+ - host: "*"
+ path: /backend/get-quote/
+ filters:
+ - name: authentication
+```
+
+If the auth service uses a framework like [Gorilla Toolkit](http://www.gorillatoolkit.org) which enforces strict slashes as HTTP path separators, it is possible to end up with an infinite redirect where the filter's framework redirects any request with non-conformant slashing. This would arise if the above example had ```path_prefix: "/extauth/"```, the filter would see a request for ```/extauth//backend/get-quote/``` which would then be redirected to ```/extauth/backend/get-quote/``` rather than actually be handled by the authentication handler. For this reason, remember that the full path of the incoming request including the leading slash, will be appended to ```path_prefix``` regardless of non-conformant slashing.
+
+## 3. Test authentication
+
+If we `curl` to a protected URL:
+
+```
+$ curl -Lv $AMBASSADORURL/backend/get-quote/
+```
+
+We get a 401 since we haven't authenticated.
+
+```
+* TCP_NODELAY set
+* Connected to 54.165.128.189 (54.165.128.189) port 32281 (#0)
+> GET /backend/get-quote/ HTTP/1.1
+> Host: 54.165.128.189:32281
+> User-Agent: curl/7.63.0
+> Accept: */*
+>
+< HTTP/1.1 401 Unauthorized
+< www-authenticate: Basic realm="Ambassador Realm"
+< content-length: 0
+< date: Thu, 23 May 2019 15:24:55 GMT
+< server: envoy
+<
+* Connection #0 to host 54.165.128.189 left intact
+```
+
+If we authenticate to the service, we will get a quote successfully:
+
+```
+$ curl -Lv -u username:password $AMBASSADORURL/backend/get-quote/
+
+* TCP_NODELAY set
+* Connected to 54.165.128.189 (54.165.128.189) port 32281 (#0)
+* Server auth using Basic with user 'username'
+> GET /backend/get-quote/ HTTP/1.1
+> Host: 54.165.128.189:32281
+> Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
+> User-Agent: curl/7.63.0
+> Accept: */*
+>
+< HTTP/1.1 200 OK
+< content-type: application/json
+< date: Thu, 23 May 2019 15:25:06 GMT
+< content-length: 172
+< x-envoy-upstream-service-time: 0
+< server: envoy
+<
+{
+ "server": "humble-blueberry-o2v493st",
+ "quote": "Nihilism gambles with lives, happiness, and even destiny itself!",
+ "time": "2019-05-23T15:25:06.544417902Z"
+* Connection #0 to host 54.165.128.189 left intact
+}
+```
+
+## What's next?
+
+* Get started with authentication by [installing $AESproductName$](../../tutorials/getting-started/).
+
+* For more details see the [`External` filter](../../topics/using/filters) documentation.
diff --git a/docs/edge-stack/3.9/howtos/external-dns.md b/docs/edge-stack/3.9/howtos/external-dns.md
new file mode 100644
index 000000000..fd75f1b47
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/external-dns.md
@@ -0,0 +1,126 @@
+import Alert from '@material-ui/lab/Alert';
+
+# ExternalDNS with $productName$
+
+[ExternalDNS](https://github.com/kubernetes-sigs/external-dns) configures your existing DNS provider to make Kubernetes resources discoverable via public DNS servers by getting resources from the Kubernetes API to create a list of DNS records.
+
+
+## Getting started
+
+### Prerequisites
+
+Before you begin, review [ExternalDNS repo's deployment instructions](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster) to get information about supported DNS providers and steps to setup ExternalDNS for your provider. Each DNS provider has its own required steps, as well as annotations, arguments, and permissions needed for the following configuration.
+
+
+### Installation
+
+Configuration for a `ServiceAccount`, `ClusterRole`, and `ClusterRoleBinding` is necessary for the ExternalDNS deployment to support compatibility with $productName$ and allow ExternalDNS to get hostnames from $productName$'s `Hosts`.
+
+The following configuration is an example configuring $productName$ - ExternalDNS integration with [AWS Route53](https://aws.amazon.com/route53/) as the DNS provider. Refer to the [ExternalDNS documentation](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster) for annotations and arguments for your DNS Provider.
+
+
+1. Create a YAML file named `externaldns-config.yaml`, and copy the following configuration into it:
+
+
+ Ensure that the apiGroups include "getambassador.io" following "networking.k8s.io", and that the resources include "hosts" after "ingresses".
+
+
+ ```yaml
+ ---
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: external-dns
+ annotations:
+ eks.amazonaws.com/role-arn: {ARN} # AWS ARN role
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: external-dns
+ rules:
+ - apiGroups: [""]
+ resources: ["services","endpoints","pods"]
+ verbs: ["get","watch","list"]
+ - apiGroups: ["extensions","networking.k8s.io", "getambassador.io"]
+ resources: ["ingresses", "hosts"]
+ verbs: ["get","watch","list"]
+ - apiGroups: [""]
+ resources: ["nodes"]
+ verbs: ["list","watch"]
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ name: external-dns-viewer
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: external-dns
+ subjects:
+ - kind: ServiceAccount
+ name: external-dns
+ namespace: default
+ ---
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: external-dns
+ spec:
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: external-dns
+ template:
+ metadata:
+ labels:
+ app: external-dns
+ annotations:
+ iam.amazonaws.com/role: {ARN} # AWS ARN role
+ spec:
+ serviceAccountName: external-dns
+ containers:
+ - name: external-dns
+ image: registry.opensource.zalan.do/teapot/external-dns:latest
+ args:
+ - --source=ambassador-host
+ - --domain-filter=example.net # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
+ - --provider=aws
+ - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
+ - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
+ - --registry=txt
+ - --txt-owner-id= {Hosted Zone ID} # Insert Route53 Hosted Zone ID here
+ ```
+
+2. Review the arguments section from the ExternalDNS deployment.
+
+ Configure or remove arguments to fit your needs. Additional arguments required for your DNS provider can be found by checking the [ExternalDNS repo's deployment instructions](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster).
+
+ * `--source=ambassador-host` - required across all DNS providers to tell ExternalDNS to look for hostnames in the $productName$ `Host` configurations.
+
+3. Apply the above config with the following command to deploy ExternalDNS to your cluster and configure support for $productName$:
+
+ ```shell
+ kubectl apply -f externaldns-ambassador.yaml
+ ```
+
+## Usage
+
+After you've applied the above configuration, ExternalDNS is ready to use. Configure a `Host` with the following annotation to allow ExternalDNS to get the IP address of your $productName$'s LoadBalancer and register it with your DNS provider:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: your-hostname
+ annotations:
+ external-dns.ambassador-service: edge-stack.ambassador
+spec:
+ acmeProvider:
+ authority: none
+ hostname: your-hostname.example.com
+```
+
+
+Victory! ExternalDNS is now running and configured to report $productName$'s IP and hostname with your DNS provider.
diff --git a/docs/edge-stack/3.9/howtos/index.md b/docs/edge-stack/3.9/howtos/index.md
new file mode 100644
index 000000000..7270d09e9
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/index.md
@@ -0,0 +1,36 @@
+# "How-to" guides
+
+These guides are designed to help users quickly accomplish common tasks. The guides assume a certain level of understanding of $productName$. Many of these guides are contributed by third parties; we welcome contributions via Pull Request at https://github.com/emissary-ingress/emissary.
+
+* Integrating with Service Mesh. $productName$ natively integrates with many service meshes.
+ * [HashiCorp Consul](consul)
+ * [Istio](istio)
+ * [Linkerd](linkerd2)
+* Distributed tracing. $productName$ natively supports a number of distributed tracing systems to enable developers to visualize request flow in microservice and service-oriented architectures.
+ * [Datadog](tracing-datadog)
+ * [Zipkin](tracing-zipkin)
+* Identity providers. $AESproductName$ integrates with a number of OAuth Identity Providers via OpenID Connect.
+ * [Auth0](sso/auth0)
+ * [Azure Active Directory](sso/azure)
+ * [Google Identity](sso/google)
+ * [Keycloak](sso/keycloak)
+ * [Okta](sso/okta)
+ * [Onelogin](sso/onelogin)
+ * [Salesforce](sso/salesforce)
+ * [UAA](sso/uaa)
+* Monitoring. $productName$ integrates with a number of different monitoring/metrics providers.
+ * [Prometheus](prometheus)
+* [Developing Custom Filters](filter-dev-guide)
+* Frameworks and Protocols. $productName$ supports a wide range of protocols and cloud-native frameworks.
+ * [gRPC](grpc)
+ * [Knative Serverless Framework](knative)
+ * [WebSockets](websockets)
+* Security. $productName$ supports a number of strategies for securing Kubernetes services.
+ * [Controlling the $productName$ 404 Page](controlling-404)
+ * [Protecting the Diagnostics Interface](protecting-diag-access)
+ * [HTTPS and TLS termination](tls-termination)
+ * [Certificate Manager](cert-manager) can be used to automatically obtain and renew TLS certificates; $AESproductName$ natively integrates this functionality.
+ * [Client Certificate Validation](client-cert-validation)
+ * [Basic Authentication](basic-auth) is a tutorial on how to use the external authentication API to code your own authentication service.
+ * [Rate Limiting in $productName$](advanced-rate-limiting)
+ * [Single Sign-On with OAuth and OpenID Connect](oauth-oidc-auth)
diff --git a/docs/edge-stack/3.9/howtos/istio.md b/docs/edge-stack/3.9/howtos/istio.md
new file mode 100644
index 000000000..4c54bd1a4
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/istio.md
@@ -0,0 +1,438 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Istio integration
+
+$productName$ and Istio: Edge Proxy and Service Mesh together in one. $productName$ is deployed at the edge of your network and routes incoming traffic to your internal services (aka "north-south" traffic). [Istio](https://istio.io/) is a service mesh for microservices, and is designed to add application-level Layer (L7) observability, routing, and resilience to service-to-service traffic (aka "east-west" traffic). Both Istio and $productName$ are built using [Envoy](https://www.envoyproxy.io).
+
+$productName$ and Istio can be deployed together on Kubernetes. In this configuration, $productName$ manages
+traditional edge functions such as authentication, TLS termination, and edge routing. Istio mediates communication
+from $productName$ to services, and communication between services.
+
+This allows the operator to have the best of both worlds: a high performance, modern edge service ($productName$) combined with a state-of-the-art service mesh (Istio). While Istio has introduced a [Gateway](https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/) abstraction, $productName$ still has a much broader feature set for edge routing than Istio. For more on this topic, see our blog post on [API Gateway vs Service Mesh](https://blog.getambassador.io/api-gateway-vs-service-mesh-104c01fa4784).
+
+This guide explains how to take advantage of both $productName$ and Istio to have complete control and observability over how requests are made in your cluster:
+
+- [Install Istio](#install-istio) and configure auto-injection
+- [Install $productName$ with Istio integration](#install-edge)
+- [Configure an mTLS `TLSContext`](#configure-an-mtls-tlscontext)
+- [Route to services using mTLS](#route-to-services-using-mtls)
+
+If desired, you may also
+
+- [Enable strict mTLS](#enable-strict-mtls)
+- [Configure Prometheus metrics collection](#configure-prometheus-metrics-collection)
+- [Configure Istio distributed tracing](#configure-istio-distributed-tracing)
+
+To follow this guide, you need:
+
+- A Kubernetes cluster version 1.15 and above
+- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
+- Istio version 1.10 or higher
+
+## Install Istio
+
+Start by [installing Istio](https://istio.io/docs/setup/getting-started/). Any supported installation method for
+Istio will work for use with $productName$.
+
+### Configure Istio Auto-Injection
+
+Istio functions by supplying a sidecar container running Envoy with every service in the mesh (including
+$productName$). The sidecar is what enforces Istio policies for traffic to and from the service, notably
+including mTLS encryption and certificate handling. As such, it is very important that the sidecar be
+correctly supplied for every service in the mesh!
+
+While it is possible to manage sidecars by hand, it is far easier to allow Istio to automatically inject
+the sidecar as necessary. To do this, set the `istio-injection` label on each Kubernetes Namespace for
+which you want auto-injection:
+
+```yaml
+kubectl label namespace $namespace istio-injection=enabled --overwrite
+```
+
+
+ The following example uses the `istio-injection` label to arrange for auto-injection in the
+ `$productNamespace$` Namespace below. You can manage sidecar injection by hand if you wish; what
+ is critical is that every service that participates in the Istio mesh have the Istio
+ sidecar.
+
+
+## Install $productName$ with Istio Integration
+
+Properly integrating $productName$ with Istio provides support for:
+
+* [Mutual TLS (mTLS)](../../topics/running/tls/mtls), with certificates managed by Istio, to allow end-to-end encryption
+for east-west traffic;
+* Automatic generation of Prometheus metrics for services; and
+* Istio distributed tracing for end-to-end observability.
+
+The simplest way to enable everything is to install $productName$ using [Helm](https://helm.sh), though
+you can use manual installation with YAML if you wish.
+
+### Installation with Helm (Recommended)
+
+To install with Helm, write the following YAML to a file called `istio-integration.yaml`:
+
+```yaml
+# All of the values we need to customize live under the emissary-ingress toplevel key.
+emissary-ingress:
+ # Listeners are required in $productName$ 2.0.
+ # This will create the two default Listeners for HTTP on port 8080 and HTTPS on port 8443.
+ createDefaultListeners: true
+
+ # These are annotations that will be added to the $productName$ pods.
+ podAnnotations:
+ # These first two annotations tell Istio not to try to do port management for the
+ # $productName$ pod itself. Though these annotations are placed on the $productName$
+ # pods, they are interpreted by Istio.
+ traffic.sidecar.istio.io/includeInboundPorts: "" # do not intercept any inbound ports
+ traffic.sidecar.istio.io/includeOutboundIPRanges: "" # do not intercept any outbound traffic
+
+ # We use proxy.istio.io/config to tell the Istio proxy to write newly-generated mTLS certificates
+ # into /etc/istio-certs, which will be mounted below. Though this annotation is placed on the
+ # $productName$ pods, it is interpreted by Istio.
+ proxy.istio.io/config: |
+ proxyMetadata:
+ OUTPUT_CERTS: /etc/istio-certs
+
+ # We use sidecar.istio.io/userVolumeMount to tell the Istio sidecars to mount the istio-certs
+ # volume at /etc/istio-certs, allowing the sidecars to see the generated certificates. Though
+ # this annotation is placed on the $productName$ pods, it is interpreted by Istio.
+ sidecar.istio.io/userVolumeMount: '[{"name": "istio-certs", "mountPath": "/etc/istio-certs"}]'
+
+ # We define a single storage volume called "istio-certs". It starts out empty, and Istio
+ # uses it to communicate mTLS certs between the Istio proxy and the Istio sidecars (see the
+ # annotations above).
+ volumes:
+ - emptyDir:
+ medium: Memory
+ name: istio-certs
+
+ # We also tell $productName$ to mount the "istio-certs" volume at /etc/istio-certs in the
+ # $productName$ pod. This gives $productName$ access to the mTLS certificates, too.
+ volumeMounts:
+ - name: istio-certs
+ mountPath: /etc/istio-certs/
+ readOnly: true
+
+ # Finally, we need to set some environment variables for $productName$.
+ env:
+ # AMBASSADOR_ISTIO_SECRET_DIR tells $productName$ to look for Istio mTLS certs, and to
+ # make them available as a secret named "istio-certs".
+ AMBASSADOR_ISTIO_SECRET_DIR: "/etc/istio-certs"
+
+ # AMBASSADOR_ENVOY_BASE_ID is set to prevent collisions with the Istio sidecar's Envoy,
+ # which runs with base-id 0.
+ AMBASSADOR_ENVOY_BASE_ID: "1"
+```
+
+To install $productName$ with Helm, use these values to configure Istio integration:
+
+1. Install $productName$ if you are not already running it by [following the quickstart](../../tutorials/getting-started):
+
+2. Enable Istio auto-injection for $productName$'s namespace:
+
+ ```bash
+ kubectl label namespace $productNamespace$ istio-injection=enabled --overwrite
+ ```
+
+3. Use Helm to configure $productName$'s Istio integration
+
+4. Use Helm to install $productName$ in $productNamespace$:
+
+ ```bash
+ helm upgrade $productHelmName$ --namespace $productNamespace$ -f istio-integration.yaml datawire/$productHelmName$ && \
+ kubectl -n $productNamespace$ wait --for condition=available --timeout=90s deploy -lapp.kubernetes.io/instance=$productDeploymentName$
+ ```
+
+### Installation Using YAML
+
+If you are not using Helm to manage your $productName$ installation, you need to manually incorporate the contents of the `istio-integration.yaml` file shown above into your deployment YAML:
+
+- `pod-annotations` should be configured as Kubernetes `annotations` on the $productName$ Pods;
+- `volumes`, `volumeMounts`, and `env` contents should be included in the $productDeploymentName$ Deployment; and
+- you must also label the $productNamespace$ Namespace for auto-injection as described above.
+
+### Configuring an Existing Installation
+
+If you have already installed $productName$ and want to enable Istio:
+
+1. Install Istio.
+2. Label the $productNamespace$ namespace for Istio auto-injection, as above.
+3. Edit the $productName$ Deployments to contain the `annotations`, `volumes`, `volumeMounts`, and `env` elements
+ shown above.
+ - If you installed with Helm, you can use `helm upgrade` with `-f istio-integration.yaml` to modify the
+ installation for you.
+4. Restart the $productName$ pods.
+
+## Configure an mTLS `TLSContext`
+
+After configuring $productName$ for Istio integration, the Istio mTLS certificates are available within
+$productName$:
+
+- Both the `istio-proxy` sidecar and $productName$ mount the `istio-certs` volume at `/etc/istio-certs`.
+- The `istio-proxy` sidecar saves the mTLS certificates into `/etc/istio-certs` (per the `OUTPUT_CERTS`
+ environment variable).
+- $productName$ reads the mTLS certificates from `/etc/istio-certs` (per the `AMBASSADOR_ISTIO_SECRET_DIR`
+ environment variable) and creates a Secret named `istio-certs`.
+
+
+ At present, the Secret name istio-certs cannot be changed.
+
+
+To make use of the `istio-certs` Secret, create a `TLSContext` referencing it:
+
+ ```yaml
+ kubectl apply -f - <
+ You must either explicitly specify port 80 in your Mapping's service
+ element, or set up the Kubernetes Service resource for your upstream service to map port
+ 443. If you don't do one of these, connections to your upstream will hang — see the
+ "Configure Service Ports" section below for more information.
+
+
+The behavior of your service will not seem to change, even though mTLS is active:
+
+ ```console
+ $ curl -k https://{{AMBASSADOR_HOST}}/backend/
+
+ {
+ "server": "bewitched-acai-5jq7q81r",
+ "quote": "A late night does not make any sense.",
+ "time": "2020-06-02T10:48:45.211178139Z"
+ }
+ ```
+
+This request first went to $productName$, which routed it over an mTLS connection to the quote service in the
+default namespace. That connection was intercepted by the `istio-proxy` which authenticated the request as
+being from $productName$, exported various metrics, and finally forwarded it on to the actual quote service.
+
+### Configure Service Ports
+
+When mTLS is active, Istio makes TLS connections to your services. Since Istio handles the TLS protocol for
+you, you don't need to modify your services — however, the TLS connection will still use port 443
+if you don't configure your `Mapping`s to _explicitly_ use port 80.
+
+If your upstream service was not written to use TLS, its `Service` resource may only map port 80. If Istio
+attempts a TLS connection on port 443 when port 443 is not defined by the `Service` resource, the connection
+will hang _even though the Istio sidecar is active_, because Kubernetes itself doesn't know how to handle
+the connection to port 443.
+
+As shown above, one simple way to deal with this situation is to explicitly specify port 80 in the `Mapping`'s
+`service`:
+
+ ```yaml
+ service: quote:80 # Be explicit about port 80.
+ ```
+
+Another way is to set up your Kubernetes `Service` to map both port 80 and port 443. For example, the
+Quote deployment (which listens on port 8080 in its pod) might use a `Service` like this:
+
+ ```yaml
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: quote
+ spec:
+ type: ClusterIP
+ selector:
+ app: quote
+ ports:
+ - name: http
+ port: 80
+ protocol: TCP
+ targetPort: 8080
+ - name: https
+ port: 443
+ protocol: TCP
+ targetPort: 8080
+ ```
+
+Note that ports 80 and 443 are both mapped to `targetPort` 8080, where the service is actually listening.
+This permits Istio routing to work whether mTLS is active or not.
+
+## Enable Strict mTLS
+
+Istio defaults to _permissive_ mTLS, where mTLS is allowed between services, but not required. Configuring
+[_strict_ mTLS](https://istio.io/docs/tasks/security/authentication/authn-policy/#globally-enabling-istio-mutual-tls-in-strict-mode) requires all connections within the cluster be encrypted. To switch Istio to use strict mTLS,
+apply a `PeerAuthentication` resource in each namespace that should operate in strict mode:
+
+ ```yaml
+ $ kubectl apply -f - <
+ secret:
+ protectedOrigins:
+ - origin: http://domain1.example.com
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: domain2-tenant
+spec:
+ OAuth2:
+ authorizationURL: https://example.auth0.com
+ extraAuthorizationParameters:
+ audience: https://example.auth0.com/api/v2/
+ clientId:
+ secret:
+ protectedOrigins:
+ - origin: http://domain2.example.com
+```
+
+Create a separate `FilterPolicy` that specifies which specific filters are applied to particular hosts or URLs.
+
+## Further reading
+
+The [filter reference](../../topics/using/filters/) covers the specifics of filters and filter policies in much more detail.
diff --git a/docs/edge-stack/3.9/howtos/sso/auth0.md b/docs/edge-stack/3.9/howtos/sso/auth0.md
new file mode 100644
index 000000000..2d5903f9e
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/auth0.md
@@ -0,0 +1,75 @@
+# Single Sign-On with Auth0
+
+With Auth0 as your IdP, you will need to create an `Application` to handle authentication requests from $AESproductName$.
+
+1. Navigate to Applications and Select "CREATE APPLICATION"
+
+ 
+
+2. In the pop-up window, give the application a name and create a "Machine to Machine App"
+
+ 
+
+3. Select the Auth0 Management API. Grant any scope values you may
+ require. (You may grant none.) The API is required so that an
+ `audience` can be specified which will result in a JWT being
+ returned rather than opaque token. A custom API can also be used.
+
+ 
+
+4. In your newly created application, click on the Settings tab, add the Domain and Callback URLs for your service and ensure the "Token Endpoint Authentication Method" is set to `Post`. The default YAML installation of $AESproductName$ uses `/.ambassador/oauth2/redirection-endpoint` for the URL, so the values should be the domain name that points to $AESproductName$, e.g., `example.com/.ambassador/oauth2/redirection-endpoint` and `example.com`.
+
+ 
+
+ Click Advanced Settings > Grant Types and check "Authorization Code"
+
+## Configure Filter and FilterPolicy
+
+Update the Auth0 `Filter` and `FilterPolicy`. You can get the `ClientID` and `secret` from your application settings:
+
+
+ 
+
+ The `audience` is the API Audience of your Auth0 Management API:
+
+ 
+
+ The `authorizationURL` is your Auth0 tenant URL.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: auth0-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: https://datawire-ambassador.auth0.com
+ extraAuthorizationParameters:
+ audience: https://datawire-ambassador.auth0.com/api/v2/
+ clientID: fCRAI7svzesD6p8Pv22wezyYXNg80Ho8
+ secret: CLIENT_SECRET
+ protectedOrigins:
+ - origin: https://datawire-ambassador.com
+ ```
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: auth0-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "openid"
+ ```
+
+ **Note:** By default, Auth0 requires the `openid` scope.
diff --git a/docs/edge-stack/3.9/howtos/sso/azure.md b/docs/edge-stack/3.9/howtos/sso/azure.md
new file mode 100644
index 000000000..5e0fc071b
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/azure.md
@@ -0,0 +1,72 @@
+# Single Sign-On with Azure Active Directory (AD)
+
+## Set up Azure AD
+
+To use Azure as your IdP, you will first need to register an OAuth application with your Azure tenant.
+
+1. Follow the steps in the Azure documentation [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) to register your application. Make sure to select "web application" (not native application) when creating your OAuth application.
+
+2. After you have registered your application, click on `App Registrations` in the navigation panel on the left and select the application you just created.
+
+3. Make a note of both the client and tenant IDs as these will be used later when configuring $AESproductName$.
+
+4. Click on `Authentication` in the left sidebar.
+
+ - Under the `Platform configurations` section, click on `+ Add a platform`, then select `Web` and add this URL `https://{{AMBASSADOR_URL}}/.ambassador/oauth2/redirection-endpoint` into the `Redirect URIs` input field
+
+ **Note:** Azure AD requires the redirect endpoint to handle TLS
+ - Make sure your application is issuing `access tokens` by clicking on the `Access tokens (used for implicit flows)` checkbox under the `Implicit grant and hybrid flows` section
+ - Finally, click on `Configure` to save your changes
+
+5. Click on `Certificates & secrets` in the left sidebar. Click `+ New client secret` and set the expiration date you wish. Copy the value of this secret somewhere. You will need it when configuring $AESproductName$.
+
+## Set Up $AESproductName$
+
+After configuring an OAuth application in Azure AD, configuring $AESproductName$ to make use of it for authentication is simple.
+
+1. Create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials from above:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: azure-ad
+ spec:
+ OAuth2:
+ # Azure AD openid-configuration endpoint can be found at https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
+ authorizationURL: https://login.microsoftonline.com/{{TENANT_ID}}/v2.0
+ # Client ID from step 3 above
+ clientID: CLIENT_ID
+ # Secret created in step 5 above
+ secret: CLIENT_SECRET
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: https://{{AMBASSADOR_URL}}
+ ```
+
+2. Create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` created above
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: azure-policy
+ spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the quote /backend/get-quote/ endpoint
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: azure-ad
+ ```
+
+3. Apply both the `Filter` and `FilterPolicy` above with `kubectl`
+
+ ```
+ kubectl apply -f azure-ad-filter.yaml
+ kubectl apply -f azure-policy.yaml
+ ```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from Azure AD.
diff --git a/docs/edge-stack/3.9/howtos/sso/google.md b/docs/edge-stack/3.9/howtos/sso/google.md
new file mode 100644
index 000000000..d16f91517
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/google.md
@@ -0,0 +1,65 @@
+# Single Sign-On with Google
+
+## Create an OAuth client in the Google API Console
+
+To use Google as an IdP for Single Sign-On, you will first need to create an OAuth web application in the Google API Console.
+
+1. Open the [Credentials page](https://console.developers.google.com/apis/credentials) in the API Console
+2. Click `Create credentials > OAuth client ID`.
+3. Select `Web application` and give it a name
+4. Under **Restrictions**, fill in the **Authorized redirect URIs** with
+
+ ```
+ http(s)://{{AMBASSADOR_URL}}/.ambassador/oauth2/redirection-endpoint
+ ```
+5. Click `Create`
+6. Record the `client ID` and `client secret` in the pop-up window. You will need these when configuring $AESproductName$
+
+## Set up $AESproductName$
+
+After creating an OAuth client in Google, configuring $AESproductName$ to make use of it for authentication is simple.
+
+1. Create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials from above:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: google
+ spec:
+ OAuth2:
+ # Google openid-configuration endpoint can be found at https://accounts.google.com/.well-known/openid-configuration
+ authorizationURL: https://accounts.google.com
+ # Client ID from step 6 above
+ clientID: CLIENT_ID
+ # Secret created in step 6 above
+ secret: CLIENT_SECRET
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: http(s)://{{AMBASSADOR_URL}}
+ ```
+2. Create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` created above
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: google-policy
+ spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the quote /backend/get-quote/ endpoint
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: google
+ ```
+3. Apply both the `Filter` and `FilterPolicy` above with `kubectl`
+
+ ```
+ kubectl apply -f google-filter.yaml
+ kubectl apply -f google-policy.yaml
+ ```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from Google.
diff --git a/docs/edge-stack/3.9/howtos/sso/keycloak.md b/docs/edge-stack/3.9/howtos/sso/keycloak.md
new file mode 100644
index 000000000..4e55cc8bc
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/keycloak.md
@@ -0,0 +1,72 @@
+# Single Sign-On with Keycloak
+
+With Keycloak as your IdP, you will need to create a `Client` to handle authentication requests from $AESproductName$. The below instructions are known to work for Keycloak 4.8.
+
+1. Under "Realm Settings", record the "Name" of the realm your client is in. This will be needed to configure your `authorizationURL`.
+2. Create a new client: navigate to Clients and select `Create`. Use the following settings:
+ - Client ID: Any value (e.g. `ambassador`); this value will be used in the `clientID` field of the Keycloak filter
+ - Client Protocol: "openid-connect"
+ - Root URL: Leave Blank
+
+3. Click Save.
+
+4. On the next screen configure the following options:
+ - Access Type: "confidential"
+ - Valid Redirect URIs: `*`
+
+5. Click Save.
+6. Navigate to the `Mappers` tab in your Client and click `Create`.
+7. Configure the following options:
+ - Protocol: "openid-connect".
+ - Name: Any string. This is just a name for the Mapper
+ - Mapper Type: select "Audience"
+ - Included Client Audience: select from the dropdown the name of your Client. This will be used as the `audience` in the Keycloak `Filter`.
+
+8. Click Save.
+
+9. Configure client scope as desired in "Client Scopes"
+ (e.g. `offline_access`). It's possible to set up Keycloak to not
+ use scope by removing all of them from "Assigned Default Client
+ Scopes".
+
+ **Note:** All "Assigned Default Client Scopes" must be included in
+ the `FilterPolicy` `scope` argument.
+
+## Configure Filter and FilterPolicy
+
+Update the Keycloak `Filter` and `FilterPolicy` with the following:
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: keycloak-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: https://{KEYCLOAK_URL}/auth/realms/{KEYCLOAK_REALM}
+ audience: ambassador
+ clientID: ambassador
+ secret: {CLIENT_SECRET}
+ protectedOrigins:
+ - origin: https://{PROTECTED_URL}
+ ```
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: keycloak-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "offline_access"
+ ```
diff --git a/docs/edge-stack/3.9/howtos/sso/okta.md b/docs/edge-stack/3.9/howtos/sso/okta.md
new file mode 100644
index 000000000..f0735012a
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/okta.md
@@ -0,0 +1,64 @@
+# Single Sign-On with Okta
+
+1. Create an OIDC application
+
+ **Note:** If you have a [standard Okta account](https://www.okta.com) you must first navigate to your Okta Org's admin portal (step 1). [Developer accounts](https://developer.okta.com) can skip to Step 2.
+
+ - Go to your org and click `Admin` in the top right corner to access the admin portal
+ - Select `Applications`
+ - Select `Add Application`
+ - Choose `Web` and `OpenID Connect`. Then click `Create`.
+ - Give it a name, enter the URL of your $AESproductName$ load balancer in `Base URIs` and the callback URL `{AMBASSADOR_URL}/.ambassador/oauth2/redirection-endpoint` as the `Login redirect URIs`
+
+2. Copy the `Client ID` and `Client Secret` and use them to fill in the `ClientID` and `Secret` of you Okta OAuth `Filter`.
+
+3. Get the `audience` configuration
+
+ - Select `API` and `Authorization Servers`
+ - You can use the default `Authorization Server` or create your own.
+ - If you are using the default, the `audience` of your Okta OAuth `Filter` is `api://default`
+ - The value of the `authorizationURL` is the `Issuer URI` of the `Authorization Server`
+
+## Configure Filter and FilterPolicy
+
+Configure your OAuth `Filter` and `FilterPolicy` with the following:
+
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: okta-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: https://{OKTA_DOMAIN}.okta.com/oauth2/default
+ audience: api://default
+ clientID: CLIENT_ID
+ secret: CLIENT_SECRET
+ protectedOrigins:
+ - origin: https://datawire-ambassador.com
+ ```
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: okta-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "openid"
+ - "profile"
+ ```
+
+**Note:** Scope values `openid` and `profile` are required at a
+minimum. Other scope values can be added to the `Authorization Server`.
diff --git a/docs/edge-stack/3.9/howtos/sso/onelogin.md b/docs/edge-stack/3.9/howtos/sso/onelogin.md
new file mode 100644
index 000000000..59d318803
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/onelogin.md
@@ -0,0 +1,93 @@
+# Single Sign-On with OneLogin
+
+OneLogin is an application that manages authentication for your users on your network, and can provide backend access to $AESproductName$.
+
+To use OneLogin with $AESproductName$:
+
+1. Create an App Connector
+2. Gather OneLogin Credentials
+3. Configure $AESproductName$
+
+## Create an App Connector
+
+To use OneLogin as your IdP, you will first need to create an OIDC custom connector and create an application from that connector.
+
+**To do so**:
+
+1. In your OneLogin portal, select **Administration** from the top right.
+2. From the top left menu, select **Applications > Custom Connectors** and click the **New Connector** button.
+3. Give your connector a name.
+4. Select the `OpenID Connect` option as your "Sign on method."
+5. Use `http(s)://{{AMBASSADOR_URL/.ambassador/oauth2/redirection-endpoint` as the value for "Redirect URI."
+6. Optionally provide a login URL.
+7. Click the **Save** button to create the connector. You will see a confirmation message.
+8. In the "More Actions" tab, select **Add App to Connector**.
+9. Select the connector you just created.
+10. Click the **Save** button.
+
+You will see a success banner, which also brings you back to the main portal page. OneLogin is now configured to function as an OIDC backend for authentication with $AESproductName$.
+
+## Gather OneLogin Credentials
+
+Next, configure $AESproductName$ to require authentication with OneLogin, so you must collect the client information credentials from the application you just created.
+
+**To do so:**
+
+1. In your OneLogin portal, go to **Administration > Applications > Applications.**
+2. Select the application you previously created.
+3. On the left, select the **SSO** tab to see the client information.
+4. Copy the value of Client ID for later use.
+5. Click the **Show Client Secret** link and copy the value for later use.
+
+## Configure $AESproductName$
+
+Now you must configure your $AESproductName$ instance to use OneLogin.
+
+1. First, create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials you copied earlier.
+
+Here is an example YAML:
+
+```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: onelogin
+ spec:
+ OAuth2:
+ # onelogin openid-configuration endpoint can be found at https://{{subdomain}}.onelogin.com/oidc/.well-known/openid-configuration
+ authorizationURL: https://{{subdomain}}.onelogin.com/oidc
+ clientID: {{Client ID}}
+ secret: {{Client Secret}}
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: httpi(s)://{{AMBASSADOR_URL}}
+```
+
+2. Next, create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` you just created.
+
+Some example YAML:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: oauth-policy
+spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the /backend/get-quote/ endpoint from the quote application
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: onelogin
+```
+
+3. Lastly, apply both the `Filter` and `FilterPolicy` you created with a `kubectl` command in your terminal:
+
+```
+kubectl apply -f onelogin-filter.yaml
+kubectl apply -f oauth-policy.yaml
+```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from OneLogin.
diff --git a/docs/edge-stack/3.9/howtos/sso/salesforce.md b/docs/edge-stack/3.9/howtos/sso/salesforce.md
new file mode 100644
index 000000000..1410a4a42
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/salesforce.md
@@ -0,0 +1,79 @@
+# Single Sign-On with Salesforce
+
+## Set up Salesforce
+
+To use Salesforce as your IdP, you will first need to register an OAuth application with your Salesforce tenant. This guide will walk you through the most basic setup via the "Salesforce Classic Experience".
+
+1. In the `Setup` page, under `Build` click the dropdown next to `Create` and select `Apps`.
+2. Under `Connected Apps` at the bottom of the page, click on `New` at the top.
+3. Fill in the following fields with whichever values you want:
+
+ - Connected App Name
+ - API Name
+ - Contact Email
+
+4. Under `API (Enable OAuth Settings)` check the box next to `Enable OAuth Settings`.
+5. Fill in the `Callback URL` section with `https://{{AMBASSADOR_HOST}}/.ambassador/oauth2/redirection-endpoint`.
+6. Under `Selected OAuth Scopes` you must select the `openid` scope
+ value at the minimum. Select any other scope values you want to
+ include in the response as well.
+7. Click `Save` and `Continue` to create the application.
+8. Record the `Consumer Key` and `Consumer Secret` values from the `API (Enable OAuth Settings)` section in the newly created application's description page.
+
+After waiting for salesforce to register the application with their servers, you should be ready to configure $AESproductName$ to Salesforce as an IdP.
+
+## Set up $AESproductName$
+
+After configuring an OAuth application in Salesforce, configuring $AESproductName$ to make use of it for authentication is simple.
+
+1. Create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials from above:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: salesforce
+ spec:
+ OAuth2:
+ # Salesforce's generic OpenID configuration endpoint at https://login.salesforce.com/ will work but you can also use your custom Salesforce domain i.e.: http://datawire.my.salesforce.com
+ authorizationURL: https://login.salesforce.com/
+ # Consumer Key from above
+ clientID: {{Consumer Key}}
+ # Consumer Secret from above
+ secret: {{Consumer Secret}}
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: https://{{AMBASSADOR_HOST}}
+ ```
+
+2. Create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` created above
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: oauth-policy
+ spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the quote /backend/get-quote/ endpoint
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: salesforce
+ # Any additional scope values granted in step 6 above can be requested with the arguments field
+ # arguments:
+ # scope:
+ # - refresh_token
+
+ ```
+
+3. Apply both the `Filter` and `FilterPolicy` above with `kubectl`
+
+ ```
+ kubectl apply -f salesforce-filter.yaml
+ kubectl apply -f oauth-policy.yaml
+ ```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from Salesforce.
diff --git a/docs/edge-stack/3.9/howtos/sso/uaa.md b/docs/edge-stack/3.9/howtos/sso/uaa.md
new file mode 100644
index 000000000..4e0ebc9ba
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/sso/uaa.md
@@ -0,0 +1,72 @@
+# SSO with User Account and Authentication Service (UAA)
+
+**IMPORTANT:** $AESproductName$ requires the IdP to return a JWT signed by the RS256 algorithm (asymmetric key). Cloud Foundry's UAA defaults to symmetric key encryption which $AESproductName$ cannot read.
+
+1. When configuring UAA, you will need to provide your own asymmetric key in a file called `uaa.yml`. For example:
+
+ ```yaml
+ jwt:
+ token:
+ signing-key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA7Z1HBM6QFqnIJ1UA3NWnYMuubt4XlfbP1/GopTWUmchKataM
+ ...
+ ...
+ QSbJdIbUBwL8BcrfNw4ebp1DgTI9F45Re+evky0A82aL0/BvBHu8og==
+ -----END RSA PRIVATE KEY-----
+ ```
+
+2. Create an OIDC Client:
+
+ ```
+ uaac client add ambassador --name ambassador-client --scope openid --authorized_grant_types authorization_code,refresh_token --redirect_uri {AMBASSADOR_URL}/.ambassador/oauth2/redirection-endpoint --secret CLIENT_SECRET
+ ```
+
+ **Note:** Change the value of `{AMBASSADOR_URL}` with the IP or DNS of your $AESproductName$ load balancer.
+
+## Configure Filter and FilterPolicy
+
+Configure your OAuth `Filter` and `FilterPolicy` with the following:
+
+ Use the clientID (`ambassador`) and secret (`CLIENT_SECRET`) from Step 2 to configure the OAuth `Filter`.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: uaa-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: {UAA_DOMAIN}
+ audience: {UAA_DOMAIN}
+ clientID: ambassador
+ secret: CLIENT_SECRET
+ protectedOrigins:
+ - origin: https://datawire-ambassador.com
+ ```
+
+ **Note:** The `authorizationURL` and `audience` are the same for UAA configuration.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: uaa-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "openid"
+ ```
+
+**Note:** The `scope` field was set when creating the client in
+Step 2. You can add any scope values you would like when creating the
+client.
diff --git a/docs/edge-stack/3.9/howtos/token-ratelimit.md b/docs/edge-stack/3.9/howtos/token-ratelimit.md
new file mode 100644
index 000000000..35912ac92
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/token-ratelimit.md
@@ -0,0 +1,154 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Rate limiting on token claims
+
+This guide applies to $AESproductName$, use of this guide on the $OSSproductName$ is not recommended.
+
+$AESproductName$ is able to perform Rate Limiting based on JWT Token claims from either a JWT or OAuth2 Filter implementation. This is because $AESproductName$ deliberately calls the `ext_authz` filter in Envoy as the first step when processing incoming requests. In $AESproductName$, the `ext_authz` filter is implemented as a [Filter resource](../../topics/using/filters/). This explicitly means that $AESproductName$ Filters are ALWAYS processed prior to RateLimit implementations. As a result, you can use the `injectRequestHeader` field in either a JWT Filter or an OAuth Filter and pass that header along to be used for RateLimiting purposes.
+
+## Prerequisites
+
+- $AESproductName$
+- A working Keycloak instance and Keycloak Filter
+- A service exposed with a Mapping and protected by a FilterPolicy
+
+We'll use Keycloak to generate tokens with unique claims. It will work in a similar manner for any claims present on a JWT token issued by any other provider. See our guide here on using Keycloak with $AESproductName$.
+
+Here is a YAML example that describes the setup:
+
+```yaml
+---
+# Mapping to expose the Quote service
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: quote-backend
+spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+---
+# Basic OAuth filter for Keycloak
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: keycloak-filter-ambassador
+spec:
+ OAuth2:
+ authorizationURL: https:///auth/realms/
+ audience:
+ clientID:
+ secret:
+ protectedOrigins:
+ - origin: https://host.example.com
+---
+# Basic FilterPolicy that covers everything
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: ambassador-policy
+spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: keycloak-filter-ambassador
+```
+
+## 1. Configure the Filter to extract the claim
+
+In order to extract the claim, we need to have the Filter use the `injectRequestHeader` config and use a golang template to pull out the exact value of the `name` claim in our access token JWT and put it in a Header for our RateLimit to catch. Configuration is similar for both [OAuth2](../../topics/using/filters/oauth2/) and [JWT](../../topics/using/filters/jwt/).
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: keycloak-filter-ambassador
+spec:
+ OAuth2:
+ authorizationURL: https:///auth/realms/
+ audience:
+ clientID:
+ secret:
+ protectedOrigins:
+ - origin: https://host.example.com
+ injectRequestHeaders:
+ - name: "x-token-name"
+ value: "{{ .token.Claims.name }}" # This extracts the "name" claim and puts it in the "x-token-name" header.
+```
+
+## 2. Add Labels to our Mapping
+
+Now that the header is properly added, we need to add a label to the Mapping of the service that we want to rate limit. This will determine if the route established by the Mapping will use a label when $AESproductName$ is processing where to send the request. If so, it will add the labels as metadata to be attached when sent to the `RateLimitService` to determine whether or not the request should be rate-limited.
+
+Use `ambassador` as the label domain, unless you have already set up $AESproductName$ to use something else.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: quote-backend
+spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - header_request_label:
+ - request_headers:
+ key: headerkey # In pattern matching, they key queried will be "headerkey" and the value
+ header_name: "x-token-name" # queried will be the value of "x-token-name" header
+```
+
+## 3. Create our RateLimit
+
+We now have appropriate labels added to the request when we send it to the rate limit service, but how do we know what rate limit to apply and how many requests should we allow before returning an error? This is where the RateLimit comes in. The RateLimit allows us to create specific rules based on the labels associated with a particular request. If a value is not specified, then each unique value of the `x-token-name` header that comes in will be associated with its own counter. So, someone with a `name` JWT claim of "Julian" will be tracked separately from "Jane".
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: token-name-rate-limit
+spec:
+ domain: ambassador
+ - name: token-name-per-minute
+ action: Enforce
+ pattern:
+ - headerkey: "" # Each unique header value of "x-token-name" will be tracked individually
+ rate: 10
+ unit: "minute" # Per-minute tracking is useful for debugging
+```
+
+## 4. Test
+
+Now we can navigate to our backend in a browser at `https://host.example.com/backend/`. After logging in, if we keep refreshing, we will find that our 11th attempt will respond with a blank page. Success!
+
+## 5. Enforce a different rate limit for a specific user
+
+We've noticed that the user "Julian" uses bad code that abuses the API and consumes way too much bandwidth with his retries. As such, we want a user with the exact `name` claim of "Julian" to only get 2 requests per minute before getting an error.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: token-name-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - name: julians-rule-enforcement
+ action: Enforce
+ pattern:
+ - headerkey: "Julian" # Only matches for x-token-name = "Julian"
+ rate: 2
+ unit: "minute"
+ - name: token-name-per-minute
+ action: Enforce
+ pattern:
+ - headerkey: "" # Each unique header value of "x-token-name" will be tracked individually
+ rate: 10
+ unit: "minute" # Per-minute tracking is useful for debugging
+```
+
+This tutorial only scratches the surface of the rate limiting capabilities of $AESproductName$. Please see our documentation [here](../../topics/using/rate-limits/) and [here](../../topics/using/rate-limits/rate-limits/) to learn more about how you can use rate limiting.
diff --git a/docs/edge-stack/3.9/howtos/web-application-firewalls-config.md b/docs/edge-stack/3.9/howtos/web-application-firewalls-config.md
new file mode 100644
index 000000000..323e3da78
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/web-application-firewalls-config.md
@@ -0,0 +1,59 @@
+---
+ Title: Configuring Web Application Firewall rules in Edge Stack
+ description: Get Web Application Firewalls quickly setup with Edge Stack and create custom firewall rules.
+---
+
+# Configuring Web Application Firewall rules in $productName$
+
+When writing your own firewall rules it's important to first take note of a few ways that $productName$'s `WebApplicationFirewalls` work.
+
+1. Requests are either denied or allowed, redirects and dropped requests are not supported
+2. If you have a rule in your firewall configuration that specifies the `deny` action and you do not specify a `status`, then we will default to
+using status code `403`.
+3. State is not preserved across the different phases of proceeing a request. For this reason it is advised to use early blocking mode
+rather than anamoly scoring mode and to avoid creating any firewall rules that require state or information created by rules in a different phase. For more information about waf phases refer to the [Coraza Seclang Execution Flow docs][].
+
+## Ambassador Labs Firewall Ruleset
+
+Ambassador Labs publishes and maintains a set of firewall rules that are ready to use.
+The latest version of the Ambassador Labs Web Application Firewall ruleset can be downloaded with these commands:
+
+```bash
+wget https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf
+wget https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf
+wget https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf
+```
+
+Each file must be imported into $productName$'s Web Application Firewall in the following order:
+
+1. aes-waf.conf
+2. crs-setup.conf
+3. waf-rules.conf
+
+The Ambassador Labs ruleset largely focuses on incoming requests and by default it does not perform processing on response bodies from upstream services to minimize the request round-trip latency.
+
+If processing of responses is desired, then you can create your own custom rule set or add additional rules to be loaded after the Ambassador Labs ruleset to add custom validation of responses from upstream services.
+
+If you are adding rules to process response bodies after the Ambassador Labs ruleset, then you will need to set `SecResponseBodyAccess On` in your rules to enable access to the response body.
+
+If you'd like to customize the Ambassador Labs default ruleset, you can load your own files before or after waf-rules.conf. Keep in mind that the `WebApplicationFirewall` resource loads firewall configurations via a list of rules sources, and sources lower in the list can overwrite rules and settings from sources higher in the list. See files [REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example][] and [RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example][] for more information.
+
+## Web Application Firewall Rules Release Notes
+
+
+To install any of the rules below, import all the files for the desired version in the order they are listed.
+
+
+### Version v1-20230825
+
+Initial version of $productName$'s Web Application Firewall rules.
+
+Files:
+
+- [aes-waf.conf](https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf)
+- [crs-setup.conf](https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf)
+- [waf-rules.conf](https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf)
+
+[REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example
+[RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
+[Coraza Seclang Execution Flow docs]: https://coraza.io/docs/seclang/execution-flow/
diff --git a/docs/edge-stack/3.9/howtos/web-application-firewalls-in-production.md b/docs/edge-stack/3.9/howtos/web-application-firewalls-in-production.md
new file mode 100644
index 000000000..4b8d61db3
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/web-application-firewalls-in-production.md
@@ -0,0 +1,223 @@
+---
+ Title: Using Web Application Firewalls in production
+ description: Learn about best practices for enabling Edge Stack's Web Application Firewalls in production environments
+---
+
+# Using Web Application Firewall in production
+
+By default, Ambassador Labs rules are configured to block malicious requests. However, when a Web Application Firewall is
+first deployed in a production environment, it is recommended to set it in a non-blocking mode and monitor its behavior
+to identify potential issues.
+
+The following procedure can be followed to deploy $productName$'s Web Application Firewall in detection-only mode and
+customize the rules.
+
+1. Enable Detection Only mode. Detection Only mode will run all rules, but won't execute any disruptive actions.
+ This is configured using the directive [SecRuleEngine][].
+
+ You also want to enable debug logs, which are necessary to identify false positives. You can them in the
+ `WebApplicationFirewall` resource as described in the [documentation][].
+
+ Optionally, Coraza debug logs can be enabled by setting the directive [SecDebugLogLevel][]. These logs are very verbose
+ but can help identify issues when the `WebApplicationFirewall` logs don't show enough information.
+
+ The following example illustrates this:
+
+ ```yaml
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: "waf-configuration"
+ data:
+ waf-overrides.conf: |
+ SecRuleEngine DetectionOnly
+ SecDebugLogLevel 4
+
+ ---
+
+ apiVersion: gateway.getambassador.io/v1alpha1
+ kind: WebApplicationFirewall
+ metadata:
+ name: "waf-rules"
+ spec:
+ firewallRules:
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf"
+ - configMapRef:
+ key: waf-overrides.conf
+ name: waf-configuration
+ sourceType: configmap
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf"
+ logging:
+ onInterrupt:
+ enabled: true
+ ```
+
+2. Identify false positives. $productName$'s container logs will have one or more entries indicating which rules
+ were applied to a request and why.
+
+ For example, the following log entry (formatted for readability) shows that a request to `https://34.123.92.3/backend/` was
+ blocked by rule 920350 because the Host header contains an IP address.
+
+ ```text
+ 2023-06-14T17:37:29.145Z INFO waf/manager.go:73 request interrupted by waf: default/example-waf
+ {
+ "message": "Host header is a numeric IP address",
+ "data": "34.123.92.3",
+ "uri": "https://34.123.92.3/backend/",
+ "disruptive": true,
+ "matchedDatas": [
+ {
+ "Variable_": 54,
+ "Key_": "Host",
+ "Value_": "34.123.92.3",
+ "Message_": "Host header is a numeric IP address",
+ "Data_": "34.123.92.3",
+ "ChainLevel_": 0
+ }
+ ],
+ "rule": {
+ "ID_": 920350,
+ "File_": "",
+ "Line_": 9892,
+ "Rev_": "",
+ "Severity_": 4,
+ "Version_": "OWASP_CRS/4.0.0-rc1",
+ "Tags_": [
+ "application-multi",
+ "language-multi",
+ "platform-multi",
+ "attack-protocol",
+ "paranoia-level/1",
+ "OWASP_CRS",
+ "capec/1000/210/272",
+ "PCI/6.5.10"
+ ],
+ "Maturity_": 0,
+ "Accuracy_": 0,
+ "Operator_": "",
+ "Phase_": 1,
+ "Raw_": "SecRule REQUEST_HEADERS:Host \"@rx (?:^([\\d.]+|\\[[\\da-f:]+\\]|[\\da-f:]+)(:[\\d]+)?$)\" \"id:920350,phase:1,block,t:none,msg:'Host header is a numeric IP address',logdata:'%{MATCHED_VAR}',tag:'application-multi',tag:'language-multi',tag:'platform-multi',tag:'attack-protocol',tag:'paranoia-level/1',tag:'OWASP_CRS',tag:'capec/1000/210/272',tag:'PCI/6.5.10',ver:'OWASP_CRS/4.0.0-rc1',severity:'WARNING',setvar:'tx.inbound_anomaly_score_pl1=+%{tx.warning_anomaly_score}'\"",
+ "SecMark_": ""
+ }
+ }
+ ```
+
+ If you enabled Coraza debug logs, use the rule ID to identify entries that are not important as follows:
+
+ - Rules in the range 900000 to 901999 define some Coraza behaviors and can be ignored.
+
+ - Rules like the one below are used to skip other rules and can be ignored as well.
+
+ ```text
+ SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:911012,phase:2,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
+ ```
+
+
+ Each Web Application Firewall configuration file has rules in predefined ranges as follows: Rules in the range
+ 900000 to 900999 are in crs-setup.conf, rules IDs 901000 to 999999 are in waf-rules.conf, and all other rules are in aes-waf.conf.
+
+
+
+## Customizing Ambassador Labs rules
+
+There are several options to configure if/when a rule runs:
+1. Disable a rule completely.
+2. Apply a rule to some requests.
+
+### Disabling a rule completely
+
+To disable a rule, follow the instructions in [RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example][], save the
+configuration as a ConfigMap, and load it after `waf-rules.conf`.
+
+For example, let's say that we want to disable the rule with ID `913110`. The first step is to create the configuration:
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: "waf-configuration"
+data:
+ disabled-rules.conf: |
+ SecRuleRemoveById 913110
+```
+
+And then load it after `waf-rules.conf`:
+
+```yaml
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewall
+metadata:
+ name: "waf-rules"
+spec:
+ firewallRules:
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf"
+ - configMapRef:
+ key: disabled-rules.conf
+ name: waf-configuration
+ sourceType: configmap
+```
+
+### Applying a rule to some requests
+
+To apply a rule only to some requests, update it as described in [REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example][] and
+load the new settings before `waf-rules.conf`.
+
+The following example shows how to disable all rules tagged `attack-sqli` when the URI does not start with '/api/':
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: "waf-configuration"
+data:
+ website-rules.conf: |
+ SecRule REQUEST_URI "!@beginsWith /api/" \
+ "id:1000,\
+ phase:2,\
+ pass,\
+ nolog,\
+ ctl:ruleRemoveByTag=attack-sqli"
+
+---
+
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewall
+metadata:
+ name: "waf-rules"
+spec:
+ firewallRules:
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf"
+ - configMapRef:
+ key: website-rules.conf
+ name: waf-configuration
+ sourceType: configmap
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf"
+```
+
+[SecRuleEngine]: https://coraza.io/docs/seclang/directives/#secruleengine
+[SecDebugLogLevel]: https://coraza.io/docs/seclang/directives/#secdebugloglevel
+[REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example
+[RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
+[documentation]: ../web-application-firewalls
diff --git a/docs/edge-stack/3.9/howtos/web-application-firewalls.md b/docs/edge-stack/3.9/howtos/web-application-firewalls.md
new file mode 100644
index 000000000..d2c6eb06f
--- /dev/null
+++ b/docs/edge-stack/3.9/howtos/web-application-firewalls.md
@@ -0,0 +1,142 @@
+---
+ Title: Protect your services with Edge Stack's Web Application Firewalls
+ description: Quickly block common attacks in the OWASP Top 10 vulnerabilities like cross-site-scripting (XSS) and SQL injection with Edge Stack's self-service Web Application Firewalls (WAF)
+---
+
+# Using Web Application Firewalls in $productName$
+
+[$productName$][] comes fully equipped with a Web Application Firewall solution (commonly referred to as WAF) that is easy to set up and can be configured to help protect your web applications by preventing and mitigating many common attacks. To accomplish this, the [Coraza Web Application Firewall library][] is used to check incoming requests against a user-defined configuration file containing rules and settings for the firewall to determine whether to allow or deny incoming requests.
+
+
+
+$productName$ also has additional authentication features such as [Filters][] and [Rate Limiting][]. When `Filters`, `Ratelimits`, and `WebApplicationFirewalls` are all used at the same time, the order of operations is as follows and is not currently configurable.
+
+1. `WebApplicationFirewalls` are always executed first
+2. `Filters` are executed next (so long as any configured `WebApplicationFirewalls` did not already reject the request)
+3. Lastly `Ratelimits` are executed (so long as any configured `WebApplicationFirewalls` and Filters did not already reject the request)
+
+## Quickstart
+
+See the [WebAplicationFirewall API reference][] and [WebAplicationFirewallPolicy API reference][]
+pages for an overview of all the supported fields of the following custom resources.
+
+1. First, start by creating your firewall configuration. The example will download [the firewall rules][] published by [Ambassador Labs][], but you are free to write your own or use the published rules as a reference.
+
+ ```yaml
+ kubectl apply -f -</test -H 'User-Agent: Arachni/0.2.1'
+ ```
+
+Congratulations, you've successfully set up a Web Application Firewall to secure all requests coming into $productName$.
+
+
+ After applying your WebApplicationFirewall and WebApplicationFirewall resources, check their statuses to make sure that they were not rejected due to any configuration errors.
+
+
+## Rules for Web Application Firewalls
+
+Since the [Coraza Web Application Firewall library][] $productName$'s Web Application Firewall implementation, the firewall rules configuration uses [Coraza's Seclang syntax][] which is compatible with the OWASP Core Rule Set.
+
+Ambassador Labs publishes and maintains a list of rules to be used with the Web Application Firewall that should be a good solution for most users and [Coraza also provides their own ruleset][] based on the [OWASP][] core rule set. It also
+satisifies [PCI 6.6][] compliance requirements.
+
+Ambassador Labs rules differ from the OWASP Core ruleset in the following areas:
+
+- WAF engine is enabled by default.
+- A more comprehensive set of rules is enabled, including rules related to compliance with PCI DSS 6.5 and 12.1 requirements.
+
+See [Configuring $productName$'s Web Application Firewall rules][] for more information about installing Ambassador Labs rules.
+
+For specific information about rule configuration, please refer to [Coraza's Seclang documentation][]
+
+## Observability
+
+To make using $productName$'s Web Application Firewall system easier and to enable automated workflows and alerts, there are three main methods of observability for Web Application Firewall behavior.
+
+### Logging
+
+ $productName$ will log information about requests approved and denied by any `WebApplicationFirewalls` along with the reason why the request was denied.
+ You can configure the logging policies in the [coraza rules configuration][] where logs are sent to and how much information is logged.
+ Ambassador Labs' default ruleset sends the WAF logs to stdout so they show up in the container logs.
+
+### Metrics
+
+ $productName$ also outputs metrics about the Web Application Firewall, including the number of requests approved and denied, and performance information.
+
+| Metric | Type | Description | |
+|-------------------------------------|-----------------------|-----------------------------------------------------------------------------------------------|
+| `waf_created_wafs` | Gauge | Number of created web application firewall |
+| `waf_managed_wafs_total` | Counter | Number of managed web application firewalls |
+| `waf_added_latency_ms` | Histogram | Added latency in milliseconds |
+| `waf_total_denied_requests_total` | Counter (with labels) | Number of requests denied by any web application firewall |
+| `waf_total_denied_responses_total` | Counter (with labels) | Number of responses denied by any web application firewall |
+| `waf_denied_breakdown_total` | Counter (with labels) | Breakdown of requests/responses denied and the web application firewall that denied them |
+| `waf_total_allowed_requests_total` | Counter (with labels) | Number of requests allowed by any web application firewall |
+| `waf_total_allowed_responses_total` | Counter (with labels) | Number of responses allowed by any web application firewall |
+| `waf_allowed_breakdown_total` | Counter (with labels) | Breakdown of requests/responses allowed and the web application firewall that allowed them |
+| `waf_errors` | Counter (with labels) | Tracker for any errors encountered by web application firewalls and the reason for the error |
+
+### Grafana Dashboard
+
+ $productName$ provides a [Grafana dashboard][] that can be imported to [Grafana][]. In addition, the dashboard has pre-built panels that help visualize the metrics that are collected about Web Application Firewall activity. For more information about getting [Prometheus][] and Grafana set up for gathering and visualizing metrics from $productName$ please refer to the [Prometheus and Grafana documentation][].
+
+[Coraza Web Application Firewall library]: https://coraza.io/docs/tutorials/introduction/
+[Filters]: ../../topics/using/filters
+[Rate limiting]: ../../topics/using/rate-limits/rate-limits#rate-limiting-reference
+[Coraza's Seclang syntax]: https://coraza.io/docs/seclang/directives/
+[Coraza also provides their own ruleset]: https://coraza.io/docs/tutorials/coreruleset/
+[Coraza's Seclang documentation]: https://coraza.io/docs/seclang/
+[OWASP]: https://owasp.org/
+[PCI 6.6]: https://listings.pcisecuritystandards.org/documents/information_supplement_6.6.pdf
+[Grafana dashboard]: https://grafana.com/grafana/dashboards/4698-ambassador-edge-stack/
+[Grafana]: https://grafana.com/
+[Prometheus]: https://prometheus.io/docs/introduction/overview/
+[Prometheus and Grafana documentation]:../prometheus
+[WebAplicationFirewall API reference]: ../../custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall
+[WebAplicationFirewallPolicy API reference]: ../../custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy
+[$productName$]: https://www.getambassador.io/products/edge-stack/api-gateway
+[Ambassador Labs]: https://www.getambassador.io/
+[Configuring $productName$'s Web Application Firewall rules]: ../web-application-firewalls-config
+[coraza rules configuration]: https://coraza.io/docs/seclang/directives/#secauditlog
+[the firewall rules]: ../web-application-firewalls-config
diff --git a/docs/edge-stack/3.9/images/Auth0_audience.png b/docs/edge-stack/3.9/images/Auth0_audience.png
new file mode 100644
index 000000000..6cb706817
Binary files /dev/null and b/docs/edge-stack/3.9/images/Auth0_audience.png differ
diff --git a/docs/edge-stack/3.9/images/Auth0_none.png b/docs/edge-stack/3.9/images/Auth0_none.png
new file mode 100644
index 000000000..1e87f6c0e
Binary files /dev/null and b/docs/edge-stack/3.9/images/Auth0_none.png differ
diff --git a/docs/edge-stack/3.9/images/Auth0_secret.png b/docs/edge-stack/3.9/images/Auth0_secret.png
new file mode 100644
index 000000000..d0636a50d
Binary files /dev/null and b/docs/edge-stack/3.9/images/Auth0_secret.png differ
diff --git a/docs/edge-stack/3.9/images/ambassador_oidc_flow.jpg b/docs/edge-stack/3.9/images/ambassador_oidc_flow.jpg
new file mode 100644
index 000000000..4f1c0c7e6
Binary files /dev/null and b/docs/edge-stack/3.9/images/ambassador_oidc_flow.jpg differ
diff --git a/docs/edge-stack/3.9/images/create-application.png b/docs/edge-stack/3.9/images/create-application.png
new file mode 100644
index 000000000..d181be2ed
Binary files /dev/null and b/docs/edge-stack/3.9/images/create-application.png differ
diff --git a/docs/edge-stack/3.9/images/docker.png b/docs/edge-stack/3.9/images/docker.png
new file mode 100644
index 000000000..1f35e5ea4
Binary files /dev/null and b/docs/edge-stack/3.9/images/docker.png differ
diff --git a/docs/edge-stack/3.9/images/edge-stack-1.13.10-consul-cert-log.png b/docs/edge-stack/3.9/images/edge-stack-1.13.10-consul-cert-log.png
new file mode 100644
index 000000000..1e045bf42
Binary files /dev/null and b/docs/edge-stack/3.9/images/edge-stack-1.13.10-consul-cert-log.png differ
diff --git a/docs/edge-stack/3.9/images/edge-stack-1.13.10-docs-timeout.png b/docs/edge-stack/3.9/images/edge-stack-1.13.10-docs-timeout.png
new file mode 100644
index 000000000..1dc9087be
Binary files /dev/null and b/docs/edge-stack/3.9/images/edge-stack-1.13.10-docs-timeout.png differ
diff --git a/docs/edge-stack/3.9/images/edge-stack-1.13.4.png b/docs/edge-stack/3.9/images/edge-stack-1.13.4.png
new file mode 100644
index 000000000..954ac1a9c
Binary files /dev/null and b/docs/edge-stack/3.9/images/edge-stack-1.13.4.png differ
diff --git a/docs/edge-stack/3.9/images/edge-stack-1.13.7-json-logging.png b/docs/edge-stack/3.9/images/edge-stack-1.13.7-json-logging.png
new file mode 100644
index 000000000..4a47cbdfc
Binary files /dev/null and b/docs/edge-stack/3.9/images/edge-stack-1.13.7-json-logging.png differ
diff --git a/docs/edge-stack/3.9/images/edge-stack-1.13.7-memory.png b/docs/edge-stack/3.9/images/edge-stack-1.13.7-memory.png
new file mode 100644
index 000000000..9c415ba36
Binary files /dev/null and b/docs/edge-stack/3.9/images/edge-stack-1.13.7-memory.png differ
diff --git a/docs/edge-stack/3.9/images/edge-stack-1.13.7-tcpmapping-consul.png b/docs/edge-stack/3.9/images/edge-stack-1.13.7-tcpmapping-consul.png
new file mode 100644
index 000000000..c455a47f1
Binary files /dev/null and b/docs/edge-stack/3.9/images/edge-stack-1.13.7-tcpmapping-consul.png differ
diff --git a/docs/edge-stack/3.9/images/edge-stack-1.13.8-cloud-bugfix.png b/docs/edge-stack/3.9/images/edge-stack-1.13.8-cloud-bugfix.png
new file mode 100644
index 000000000..6beaf653b
Binary files /dev/null and b/docs/edge-stack/3.9/images/edge-stack-1.13.8-cloud-bugfix.png differ
diff --git a/docs/edge-stack/3.9/images/emissary-1.13.10-cors-origin.png b/docs/edge-stack/3.9/images/emissary-1.13.10-cors-origin.png
new file mode 100644
index 000000000..b7538e5f4
Binary files /dev/null and b/docs/edge-stack/3.9/images/emissary-1.13.10-cors-origin.png differ
diff --git a/docs/edge-stack/3.9/images/helm-navy.png b/docs/edge-stack/3.9/images/helm-navy.png
new file mode 100644
index 000000000..a97101435
Binary files /dev/null and b/docs/edge-stack/3.9/images/helm-navy.png differ
diff --git a/docs/edge-stack/3.9/images/jaeger.png b/docs/edge-stack/3.9/images/jaeger.png
new file mode 100644
index 000000000..3b821c09e
Binary files /dev/null and b/docs/edge-stack/3.9/images/jaeger.png differ
diff --git a/docs/edge-stack/3.9/images/kubernetes.png b/docs/edge-stack/3.9/images/kubernetes.png
new file mode 100644
index 000000000..a392a886b
Binary files /dev/null and b/docs/edge-stack/3.9/images/kubernetes.png differ
diff --git a/docs/edge-stack/3.9/images/machine-machine.png b/docs/edge-stack/3.9/images/machine-machine.png
new file mode 100644
index 000000000..32a112f9c
Binary files /dev/null and b/docs/edge-stack/3.9/images/machine-machine.png differ
diff --git a/docs/edge-stack/3.9/images/mapping-editor.png b/docs/edge-stack/3.9/images/mapping-editor.png
new file mode 100644
index 000000000..f8b751a19
Binary files /dev/null and b/docs/edge-stack/3.9/images/mapping-editor.png differ
diff --git a/docs/edge-stack/3.9/images/scopes.png b/docs/edge-stack/3.9/images/scopes.png
new file mode 100644
index 000000000..f78d22a0c
Binary files /dev/null and b/docs/edge-stack/3.9/images/scopes.png differ
diff --git a/docs/edge-stack/3.9/images/xkcd.png b/docs/edge-stack/3.9/images/xkcd.png
new file mode 100644
index 000000000..ed0d5c33b
Binary files /dev/null and b/docs/edge-stack/3.9/images/xkcd.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-1.13.4.png b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.4.png
new file mode 100644
index 000000000..954ac1a9c
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.4.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-json-logging.png b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-json-logging.png
new file mode 100644
index 000000000..4a47cbdfc
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-json-logging.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-memory.png b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-memory.png
new file mode 100644
index 000000000..9c415ba36
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-memory.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-tcpmapping-consul.png b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-tcpmapping-consul.png
new file mode 100644
index 000000000..c455a47f1
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.7-tcpmapping-consul.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-1.13.8-cloud-bugfix.png b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.8-cloud-bugfix.png
new file mode 100644
index 000000000..6beaf653b
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-1.13.8-cloud-bugfix.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-host_crd.png b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-host_crd.png
new file mode 100644
index 000000000..c77ef5287
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-host_crd.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-ingressstatus.png b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-ingressstatus.png
new file mode 100644
index 000000000..6856d308d
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-ingressstatus.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-insecure_action_hosts.png b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-insecure_action_hosts.png
new file mode 100644
index 000000000..79c20bad1
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-insecure_action_hosts.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-listener.png b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-listener.png
new file mode 100644
index 000000000..ea45a02ba
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-listener.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-prune_routes.png b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-prune_routes.png
new file mode 100644
index 000000000..bc43229fc
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-prune_routes.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-tlscontext.png b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-tlscontext.png
new file mode 100644
index 000000000..68dbad807
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-tlscontext.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-v3alpha1.png b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-v3alpha1.png
new file mode 100644
index 000000000..c0ac35962
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-2.0.0-v3alpha1.png differ
diff --git a/docs/edge-stack/3.9/release-notes/edge-stack-GA.png b/docs/edge-stack/3.9/release-notes/edge-stack-GA.png
new file mode 100644
index 000000000..2e6341881
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/edge-stack-GA.png differ
diff --git a/docs/edge-stack/3.9/release-notes/emissary-1.13.10-cors-origin.png b/docs/edge-stack/3.9/release-notes/emissary-1.13.10-cors-origin.png
new file mode 100644
index 000000000..b7538e5f4
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/emissary-1.13.10-cors-origin.png differ
diff --git a/docs/edge-stack/3.9/release-notes/tada.png b/docs/edge-stack/3.9/release-notes/tada.png
new file mode 100644
index 000000000..c8832e8e3
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/tada.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.4-k8s-1.22.png b/docs/edge-stack/3.9/release-notes/v2.0.4-k8s-1.22.png
new file mode 100644
index 000000000..ed9b04158
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.4-k8s-1.22.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.4-l7depth.png b/docs/edge-stack/3.9/release-notes/v2.0.4-l7depth.png
new file mode 100644
index 000000000..9314324cb
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.4-l7depth.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.4-mapping-dns-type.png b/docs/edge-stack/3.9/release-notes/v2.0.4-mapping-dns-type.png
new file mode 100644
index 000000000..7770c77d2
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.4-mapping-dns-type.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.4-v3alpha1.png b/docs/edge-stack/3.9/release-notes/v2.0.4-v3alpha1.png
new file mode 100644
index 000000000..9c50b8fb8
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.4-v3alpha1.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.4-version.png b/docs/edge-stack/3.9/release-notes/v2.0.4-version.png
new file mode 100644
index 000000000..9481b7dbd
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.4-version.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.5-auth-circuit-breaker.png b/docs/edge-stack/3.9/release-notes/v2.0.5-auth-circuit-breaker.png
new file mode 100644
index 000000000..cac8cf7b2
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.5-auth-circuit-breaker.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.5-cache-change.png b/docs/edge-stack/3.9/release-notes/v2.0.5-cache-change.png
new file mode 100644
index 000000000..8471ab3fa
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.5-cache-change.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.0.5-mappingselector.png b/docs/edge-stack/3.9/release-notes/v2.0.5-mappingselector.png
new file mode 100644
index 000000000..31942ede6
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.0.5-mappingselector.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.0-canary.png b/docs/edge-stack/3.9/release-notes/v2.1.0-canary.png
new file mode 100644
index 000000000..39d3bbbfb
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.0-canary.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.0-edge-stack-validation.png b/docs/edge-stack/3.9/release-notes/v2.1.0-edge-stack-validation.png
new file mode 100644
index 000000000..dc82e2821
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.0-edge-stack-validation.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.0-gzip-enabled.png b/docs/edge-stack/3.9/release-notes/v2.1.0-gzip-enabled.png
new file mode 100644
index 000000000..061fcbc97
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.0-gzip-enabled.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.0-smoother-migration.png b/docs/edge-stack/3.9/release-notes/v2.1.0-smoother-migration.png
new file mode 100644
index 000000000..ebd77497d
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.0-smoother-migration.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.2-annotations.png b/docs/edge-stack/3.9/release-notes/v2.1.2-annotations.png
new file mode 100644
index 000000000..b5498c3c1
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.2-annotations.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.2-filter-jwtassertion.png b/docs/edge-stack/3.9/release-notes/v2.1.2-filter-jwtassertion.png
new file mode 100644
index 000000000..da58bdd91
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.2-filter-jwtassertion.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.2-host-mapping-matching.png b/docs/edge-stack/3.9/release-notes/v2.1.2-host-mapping-matching.png
new file mode 100644
index 000000000..1cfba5ede
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.2-host-mapping-matching.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-cors.png b/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-cors.png
new file mode 100644
index 000000000..f76ea01ca
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-cors.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-less-weighted.png b/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-less-weighted.png
new file mode 100644
index 000000000..7e299062e
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-less-weighted.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-no-rewrite.png b/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-no-rewrite.png
new file mode 100644
index 000000000..5d3d5a29f
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.1.2-mapping-no-rewrite.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.2.0-cloud.png b/docs/edge-stack/3.9/release-notes/v2.2.0-cloud.png
new file mode 100644
index 000000000..5923fcb44
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.2.0-cloud.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.2.0-percent-escape.png b/docs/edge-stack/3.9/release-notes/v2.2.0-percent-escape.png
new file mode 100644
index 000000000..df4d81b94
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.2.0-percent-escape.png differ
diff --git a/docs/edge-stack/3.9/release-notes/v2.2.0-tls-cert-validation.png b/docs/edge-stack/3.9/release-notes/v2.2.0-tls-cert-validation.png
new file mode 100644
index 000000000..f8635b5af
Binary files /dev/null and b/docs/edge-stack/3.9/release-notes/v2.2.0-tls-cert-validation.png differ
diff --git a/docs/edge-stack/3.9/releaseNotes.yml b/docs/edge-stack/3.9/releaseNotes.yml
new file mode 100644
index 000000000..5fc4a845b
--- /dev/null
+++ b/docs/edge-stack/3.9/releaseNotes.yml
@@ -0,0 +1,2021 @@
+# -*- fill-column: 100 -*-
+
+# This file should be placed in the folder for the version of the
+# product that's meant to be documented. A `/release-notes` page will
+# be automatically generated and populated at build time.
+#
+# Note that an entry needs to be added to the `doc-links.yml` file in
+# order to surface the release notes in the table of contents.
+#
+# The YAML in this file should contain:
+#
+# changelog: An (optional) URL to the CHANGELOG for the product.
+# items: An array of releases with the following attributes:
+# - version: The (optional) version number of the release, if applicable.
+# - date: The date of the release in the format YYYY-MM-DD.
+# - notes: An array of noteworthy changes included in the release, each having the following attributes:
+# - type: The type of change, one of `bugfix`, `feature`, `security` or `change`.
+# - title: A short title of the noteworthy change.
+# - body: >-
+# Two or three sentences describing the change and why it
+# is noteworthy. This is HTML, not plain text or
+# markdown. It is handy to use YAML's ">-" feature to
+# allow line-wrapping.
+# - image: >-
+# The URL of an image that visually represents the
+# noteworthy change. This path is relative to the
+# `release-notes` directory; if this file is
+# `FOO/releaseNotes.yml`, then the image paths are
+# relative to `FOO/release-notes/`.
+# - docs: The path to the documentation page where additional information can be found.
+# - href: A path from the root to a resource on the getambassador website, takes precedence over a docs link.
+
+changelog: https://github.com/datawire/edge-stack/blob/$branch$/CHANGELOG.md
+items:
+ - version: 3.9.0
+ date: '2023-11-13'
+ notes:
+ - title: gateway.getambassador.io/v1alpha1 Filter & FilterPolicy resources
+ type: feature
+ body: >-
+ Filter and FilterPolicy resources are now available via gateway.getambassador.io/v1alpha1. It is NOT backwards compatible with getambassador.io/v3alpha1 Filter and FilterPolicy resources. These are the next generation of CRD's that you can
+ progressively adopt over time. They provide stronger typings so that
+ feedback is given at apply time rather than runtime.
+ docs: custom-resources/gateway-getambassador/v1alpha1/filter
+
+ - title: getambassador.io/v3alpha1 Filter & FilterPolicy statuses
+ type: feature
+ body: >-
+ Filter and FilterPolicy resources for the getambassador.io/v3alpha1 version will now provide statuses when they are "Ready". If there are
+ configuration errors they will provide an error message with details about the configuration issues. This will help troubleshoot configuration issues.
+
+ docs: custom-resources/getambassador/v3alpha1/filter
+
+ - title: Upgrade to Envoy 1.27.2
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.27.2 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.27.2 Release Notes
+ docs: https://www.envoyproxy.io/docs/envoy/v1.27.2/version_history/version_history
+
+ - title: Added support for RESOURCE_EXHAUSTED responses to grpc clients when rate limited
+ type: feature
+ body: >-
+ By default, $productName$ will return an UNAVAILABLE code when a request using gRPC is rate limited. The RateLimitService resource now exposes a new grpc.use_resource_exhausted_code field that when set to true, $productName$ will return a RESOURCE_EXHAUSTED gRPC code instead. Thanks to Jerome Froelich for contributing this feature!
+ docs: topics/using/running/aes-extensions/ratelimit
+
+ - title: Added support for setting specific Envoy runtime flags in the Module
+ type: feature
+ body: >-
+ Envoy runtime fields that were provided to mitigate the recent HTTP/2 rapid reset
+ vulnerability can now be configured via the Module resource so the configuration will
+ persist between restarts. This configuration is added to the Envoy bootstrap config, so
+ restarting Emissary is necessary after changing these fields for the configuration to take effect.
+ docs: topics/running/ambassador/#set-envoy-runtime-flags
+
+ - title: Update APIExt minimum TLS version
+ type: change
+ body: >-
+ APIExt would previously allow for TLS 1.0 connections. We have updated it to now only use a minimum TLS version of 1.3 to resolve security concerns.
+ docs: https://www.tenable.com/plugins/nessus/104743
+
+ - title: Shipped Helm chart v8.9.0
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.9.0.
+ docs: https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$
+
+ - title: Ensure APIExt server is available before starting Edge Stack
+ type: bugfix
+ body: >-
+ The APIExt server provides CRD conversion between the stored version
+ v2 and the version watched for by $productName$ v3alpha1. Since this
+ component is required to operate $productName$, we have introduced
+ an init container that will ensure it is available before starting. This will help address some of the intermittent issues seen during
+ install and upgrades.
+ docs: https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$
+
+ - version: 3.8.2
+ date: '2023-10-11'
+ notes:
+ - title: Upgrade Envoy
+ type: security
+ body: >-
+ This release includes security patches to the current Envoy proxy version to address CVE 2023-44487 and includes a fix to determine if a client is making too many requests with premature resets. The connection is disconnected if more than 50 percent of resets are considered premature. Another fix is also included which exposes a runtime setting to control the limit on the number of HTTP requests processed from a single connection in a single I/O cycle to mitigate CPU starvation.
+ docs: topics/running/running/
+
+ - title: Upgrade Golang to 1.20.10
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This update resolves CVE-2023-39323 and CVE-2023-39325.
+ docs: https://go.dev/doc/devel/release#go1.20.minor
+
+ - version: 3.8.1
+ date: '2023-09-18'
+ notes:
+ - title: Upgrade Golang to 1.20.8
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This includes security fixes for CVE-2023-39318, CVE-2023-39319.
+ docs: https://go.dev/doc/devel/release#go1.20.minor
+
+ - version: 3.8.0
+ date: '2023-08-29'
+ notes:
+ - title: Ambassador Edge Stack will fail to run if a valid license is not present
+ type: change
+ body: >-
+ $productName$ will now require a valid non-expired license to run the product. If a valid license is not present or your clusters are not connected to and showing licensed in Ambassador Cloud, then $productName$ will refuse to startup. If you already have an enterprise license then you do not need to do anything so long as it is properly applied and not expired. Please view the license documentation page for more information on your license.
+ If you do not have an enterprise license for $productName$ then you can visit the quickstart guide to get setup with a free community license by signing into Ambassador Cloud and connecting your installation.
+ docs: tutorials/getting-started/
+ - title: Account for matchLabels when associating mappings with the same prefix to different Hosts
+ type: bugfix
+ body: >-
+ As of v2.2.2, if two mappings were associated with different Hosts through host
+ mappingSelector labels but share the same prefix, the labels were not taken into
+ account which would cause one Mapping to be correctly routed but the other not.
+
+ This change fixes this issue so that Mappings sharing the same prefix but associated
+ with different Hosts will be correctly routed.
+ docs: https://github.com/emissary-ingress/emissary/issues/4170
+ - title: Duplication of values when using multiple Headers/QueryParameters in Mappings
+ type: bugfix
+ body: >-
+ In previous versions, if multiple Headers/QueryParameters were used in a v3alpha1 mapping,
+ these values would duplicate and cause all the Headers/QueryParameters to have the same value.
+ This is no longer the case and the expected values for unique Headers/QueryParameters will apply.
+
+ This issue was only present in v3alpha1 Mappings. For users who may have this issue, please
+ be sure to re-apply any v3alpha1 Mappings in order to update the stored v2 Mapping and resolve the
+ issue.
+ docs: topics/using/headers/headers
+ - title: Ambassador Agent no longer collects Envoy metrics
+ type: change
+ body: >-
+ When the Ambassador agent is being used, it will no longer attempt to collect and report Envoy metrics.
+ In previous versions, $productName$ would always create an Envoy stats sink for the agent as long as the AMBASSADOR_GRPC_METRICS_SINK
+ environment variable was provided. This environment variable was hardcoded on the release manifests and has now been removed
+ and an Envoy stats sink for the agent is no longer created.
+ docs: topics/running/environment#ambassador_grpc_metrics_sink
+ - version: 3.7.2
+ date: '2023-07-25'
+ notes:
+ - title: Upgrade to Envoy 1.26.4
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.26.4 which includes a fixes for CVE-2023-35942, CVE-2023-35943, CVE-2023-35944.
+ docs: https://www.envoyproxy.io/docs/envoy/v1.26.1/version_history/v1.26/v1.26
+
+ - title: Shipped Helm chart v8.7.2
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.7.2.
+ docs: https://github.com/datawire/edge-stack/blob/rel/v3.7.2/charts/edge-stack/CHANGELOG.md
+
+ - version: 3.7.1
+ date: '2023-07-13'
+ notes:
+ - title: Upgrade to Envoy 1.26.3
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.26.3 which includes a fix for CVE-2023-35945.
+ docs: https://www.envoyproxy.io/docs/envoy/v1.26.1/version_history/v1.26/v1.26
+
+ - version: 3.7.0
+ date: '2023-06-20'
+ notes:
+ - title: Configurable Web Application Firewalls
+ type: feature
+ body: >-
+ $productName$ now provides configurable Web Application Firewalls (WAFs) that can be used to add additional security to your services by blocking dangerous requests. They can be configured globally or route by route. We have also published a ready to use set of rules to get you started and protected against the OWASP Top 10
+ vulnerabilities and adheres to PCI 6.6 requirements.
+ The published rule set will be updated and maintained regularly.
+ docs: howtos/web-application-firewalls/
+
+ - title: Upgrade to Envoy 1.26.1
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.26.1 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.26.1 Release Notes
+ docs: https://www.envoyproxy.io/docs/envoy/v1.26.1/version_history/v1.26/v1.26
+
+ - title: ExternalFilter - Add support for configuring TLS Settings
+ type: feature
+ body: >-
+ The ExternalFilter now supports configuring a CA certificate and/or client certificate via the new tlsConfig attribute. This allows $productName$ to communicate with the configured AuthService using custom TLS certificates signed by a different CA. It also allows the ExternalFilter to originate mTLS and have $productName$ present mTLS client certificates to the AuthService. Custom TLS certificates are provided as Kubernetes Secrets.
+ docs: topics/using/filters/external/#configuring-tls-settings
+
+ - version: 3.6.0
+ date: '2023-04-17'
+ notes:
+ - title: Deprecation of insteadOfRedirect.filters argument in FilterPolicy
+ type: change
+ body: >-
+ The insteadOfRedirect.filters field within the OAuth2 path-specific arguments has been deprecated and it will be fully removed in a future version of $productName$. Similiar behavior can
+ be accomplished using onDeny=continue and chaining a
+ fallback Filter to run.
+ docs: topics/using/filters/oauth2#oauth2-path-specific-arguments
+
+ - title: Upgrade to Envoy 1.25.4
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.25.4 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.25.4 Release Notes
+ docs: https://www.envoyproxy.io/docs/envoy/v1.25.4/version_history/v1.25/v1.25
+
+ - title: Shipped Helm chart v8.6.0
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.6.0.
+
+ - Add support for setting nodeSelector, tolerations and affinity on the Ambassador Agent. Thanks to Philip Panyukov.
+
+ - Use autoscaling API version based on Kubernetes version. Thanks to Elvind Valderhaug.
+
+ - Upgrade KubernetesEndpointResolver & ConsulResolver apiVersions to getambassador.io/v3alpha1
+
+ docs: https://github.com/emissary-ingress/emissary/blob/master/charts/emissary-ingress/CHANGELOG.md
+
+ - version: 3.5.2
+ date: "2023-04-05"
+ notes:
+ - title: Upgrade to Envoy 1.24.5
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.24.5. This update includes various security patches including CVE-2023-27487, CVE-2023-27491, CVE-2023-27492, CVE-2023-27493, CVE-2023-27488, and CVE-2023-27496. It also contains the dependency update for c-ares which was patched on top.
+
+ One notable item is that upstream header names and values are now validated according to RFC 7230, section 3.2. Users utilizing external filters should check whether their external service is forwarding headers containing forbidden characters
+ - title: Upgrade to Golang 1.20.3
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This includes security fixes for CVE-2023-24537, CVE-2023-24538, CVE-2023-24534, CVE-2023-24536.
+
+ - version: 3.5.1
+ date: '2023-02-24'
+ notes:
+ - title: Fix regression with ExternalFilter parsing port incorrectly
+ type: bugfix
+ body: >-
+ A regression with parsing the authService field of the ExternalFilter has been fixed. This would cause the ExternalFilter to fail without sending a request to the service causing a 403 response.
+
+ - title: Shipped Helm chart v8.5.1
+ type: bugfix
+ body: >-
+ Fix regression where the Module resource fails validation when setting the ambassador_id after upgrading to getambassador.io/v3alpha1.
+
+ Thanks to Pier.
+
+ docs: https://github.com/datawire/edge-stack
+
+ - version: 3.5.0
+ date: '2023-02-15'
+ notes:
+ - title: Upgraded to golang 1.20.1
+ type: security
+ body: >-
+ Upgraded to the latest release of Golang as part of our general dependency upgrade process. This includes
+ security fixes for CVE-2022-41725, CVE-2022-41723.
+
+ - title: TracingService support for native OpenTelemetry driver
+ type: feature
+ body: >-
+ In Envoy 1.24, experimental support for a native OpenTelemetry tracing driver was introduced that allows exporting spans in the otlp format. Many observability platforms accept that format and is the recommended replacement for the LightStep driver. $productName$ now supports setting the TracingService.spec.driver=opentelemetry to export traces in the otlp format.
+
+ Thanks to Paul for helping us get this tested and over the finish line!
+
+ - title: Switch to a non-blocking readiness check
+ type: feature
+ body: >-
+ The /ready endpoint used by $productName$ was using the Envoy admin port (8001 by default).This generates a problem during config reloads with large configs as the admin thread is blocking so the /ready endpoint can be very slow to answer (in the order of several seconds, even more).
+
+ $productName$ will now use a specific envoy listener that can answer /ready calls from an Envoy worker thread so the endpoint is always fast and it does not suffer from single threaded admin thread slowness on config reloads and other slow endpoints handled by the admin thread.
+
+ Configure the listener port using AMBASSADOR_READY_PORT and enable access log using AMBASSADOR_READY_LOG environment variables.
+
+ docs: https://www.getambassador.io/docs/edge-stack/latest/topics/running/environment/
+
+ - title: Fix envoy config generated when including port in Host.hostname
+ type: bugfix
+ body: >-
+ When wanting to expose traffic to clients on ports other than 80/443, users will set a port in the Host.hostname (eg.Host.hostname=example.com:8500). The config generated allowed matching on the :authority header. This worked in v1.Y series due to the way $productName$ was generating Envoy configuration under a single wild-card virtual_host and matching on :authority.
+
+ In v2.Y/v3.Y+, the way $productName$ generates Envoy configuration changed to address memory pressure and improve route lookup speed in Envoy. However, when including a port in the hostname, an incorrect configuration was generated with an sni match including the port. This caused incoming request to never match causing a 404 Not Found.This has been fixed and the correct envoy configuration is
+ being generated which restores existing behavior.
+
+ - title: Fix GRPC TLS support with ExternalFilter
+ type: bugfix
+ body: >-
+ Configuring an ExternalFilter to communicate using gRPC with TLS would fail due to $productName$ trying to connect via cleartext. This has been fixed so that setting ExternalFilter.spec.tls=true $productName$ will talk to the external filter using TLS.
+
+ If using self-signed certs see installing self-signed certificates on how to include it into the $productName$ deployment.
+
+ - title: Add support for resolving port names in Ingress resource
+ type: change
+ body: >-
+ Previously, specifying backend ports by name in Ingress was not supported and would result in defaulting to port 80. This allows $productName$ to now resolve port names for backend services. If the port number cannot be resolved by the name (e.g named port in the Service doesn't exist) then it will continue to default back
+ to port 80.
+
+ Thanks to Anton Ustyuzhanin!.
+ github:
+ - title: "#4809"
+ link: https://github.com/emissary-ingress/emissary/pull/4809
+
+ - title: Upgraded to Python 3.10
+ type: change
+ body: >-
+ Upgraded to Python 3.10 as part of continued investment in keeping dependencies updated.
+
+ - title: Upgraded base image to alpine-3.17
+ type: change
+ body: >-
+ Upgraded base image to alpine-3.17 as part of continued investment in keeping dependencies updated.
+
+ - title: Shipped Helm chart v8.5.0
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.5.0.
+
+ - Add support for configuring startupProbes on the $productName$ deployment.
+
+ - Allow setting pod and container security settings on the Ambassador Agent.
+
+ - Added new securityContext fields to the Redis and Agent helm charts, allowing users to further manage privilege and access control settings which can be used for tools such as PodSecurityPolicy.
+
+ - Added deprecation notice in the values.yaml file for podSecurityPolicy value because support has been removed in Kubernetes 1.25.
+
+ docs: https://github.com/datawire/edge-stack
+
+ - version: 3.4.1
+ date: '2023-02-07'
+ notes:
+ - title: Upgrade to Envoy 1.24.2
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.24.2. This update addresses the folowing notable items:
+
+ - Updates boringssl to address High CVE-2023-0286
+ - Updates c-ares dependency to address issue with cname wildcard dns resolution for upstream clusters
+
+ Users that are using $productName$ with Certificate Revocation List and allow external users to provide input should upgrade to ensure they are not vulnerable to CVE-2023-0286.
+
+ - version: 3.4.0
+ date: '2023-01-03'
+ notes:
+ - title: Upgrade to Envoy 1.24.1
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.24.1. Two notable changes were introduced:
+
+ First, the team at LightStep and the Envoy Maintainers have decided to no longer support the native LightStep tracing driver in favor of using the Open Telemetry driver. The code for the native Enovy LightStep driver has been removed from the Envoy code base. This means $productName$ will no longer support LightStep in the TracingService. The recommended upgrade path is to leverage a supported Tracing driver such as Zipkin and use the Open Telemetry Collector to collect and forward Observabity data to LightStep. A guide for this can be found here: Distributed Tracing with Open Telemetry and LightStep.
+
+ Second, a bug was fixed in Envoy 1.24 that changes how the upstream clusters distributed tracing span is named. Prior to Envoy 1.24 it would always set the span name to the cluster.name. The expected behavior from Envoy was that if provided an alt_stat_name then use it else fallback to cluster.name.
+
+ docs: https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.24/v1.24
+
+ - title: Re-add support for getambassador.io/v1
+ type: feature
+ body: >-
+ Support for the getambassador.io/v1 apiVersion has been re-introduced, in order to facilitate smoother migrations from $productName$ 1.y. Previously, in order to make migrations possible, an "unserved" v1 version was declared to Kubernetes, but was unsupported by $productName$. That unserved v1 could
+ cause an excess of errors to be logged by the Kubernetes Nodes (regardless of whether the installation was migrated from 1.y or was a fresh 2.y install).
+
+ It is still recommeded that `v3alpha1` be used but fully supporting v1 again should resolve these errors.
+ docs: https://github.com/emissary-ingress/emissary/pull/4055
+
+ - title: Add support for active health checking configuration.
+ type: feature
+ body: >-
+ It is now possible to configure active healhchecking for upstreams within a Mapping. If the upstream fails its configured health check then Envoy will mark the upstream as unhealthy and no longer send traffic to that upstream. Single pods within a group may can be marked as unhealthy. The healthy pods will continue to receive
+ traffic normally while the unhealthy pods will not receive any traffic until they recover by passing the health check.
+ docs: howtos/active-health-checking/
+
+ - title: Add environment variables to the healthcheck server.
+ type: feature
+ body: >-
+ The healthcheck server's bind address, bind port and IP family can now be configured using environment variables:
+
+ AMBASSADOR_HEALTHCHECK_BIND_ADDRESS: The address to bind the healthcheck server to.
+
+ AMBASSADOR_HEALTHCHECK_BIND_PORT: The port to bind the healthcheck server to.
+
+ AMBASSADOR_HEALTHCHECK_IP_FAMILY: The IP family to use for the healthcheck server.
+
+ This allows the healthcheck server to be configured to use IPv6-only k8s environments. (Thanks to Dmitry Golushko!).
+
+ - title: Added metrics for External Filters to the /metrics endpoint
+ type: feature
+ body: >-
+ $productName$ now tracks metrics for External Filters which includes responses approved/denied, the response codes returned as well as configuration and connection errors.
+ docs: topics/using/filters/external/#metrics
+
+ - title: Allow setting the OAuth2 client's session max idle time
+ type: feature
+ body: >-
+ When using the OAuth2 Filter, $productName$ creates a new session when submitting requests to the upstream backend server and sets a cookie containing the sessionID. This session has a limited lifetime before it expires or is extended, prompting the user to log back in.
+
+ This session idle length can now be controlled under a new field in the OAuth2 Filter, clientSessionMaxIdle, which controls how long the session will be active without activity before it is expired.
+ docs: topics/using/filters/oauth2
+
+ - title: Updated redis client to improve performance with Redis
+ type: change
+ body: >-
+ We have updated the client library used to communicate with Redis. The new client provides support for better connection handling and sharing and improved overall performance. As part of our update to
+ the new driver we reduced chattiness with Redis by taking advantage
+ of Pipelinig and Scripting features of Redis.
+
+ This means the AES_REDIS_EXPERIMENTAL_DRIVER_ENABLED flag is now a no-op and can be safely removed.
+
+ - title: Adopt stand alone Ambassador Agent
+ type: change
+ body: >-
+ Previously, the Agent used for communicating with Ambassador Cloud was bundled into $productName$. This tied it to the same release schedule as $productName$ and made it difficult to iterate on its feature set. It has now been extracted into its own repository and has its own release process and schedule.
+ docs: https://github.com/datawire/ambassador-agent
+
+ - title: Fix Filters not properly caching large jwks responses
+ type: bugfix
+ body: >-
+ In some cases, a Filter would fail to properly cache the response from the jwks endpoint due to the response being too large to cache. This would hurt performance and cause $productName$ to be rate-limited by the iDP. This has been fixed to accomodate iDP's that are configured to support multiple key sets thus returning a response that is larger than the typical default response from most iDP's.
+
+ - version: 3.3.1
+ date: '2022-12-08'
+ notes:
+ - title: Update Golang to 1.19.4
+ type: security
+ body: >-
+ Updated Golang to latest 1.19.4 patch release which contained two CVEs: CVE-2022-41720, CVE-2022-41717.
+
+ CVE-2022-41720 only affects Windows and $productName$ only ships on Linux. CVE-2022-41717 affects HTTP/2 servers that are exposed to external clients. By default, $productName$ exposes the endpoints for DevPortal, Authentication Service, and RateLimitService via Envoy. Envoy enforces a limit on request header size which mitigates the vulnerability.
+
+ - version: 3.3.0
+ date: '2022-11-02'
+ notes:
+ - title: Update Golang to 1.19.2
+ type: security
+ body: >-
+ Updated Golang to 1.19.2 to address the CVEs: CVE-2022-2879, CVE-2022-2880, CVE-2022-41715.
+
+ - title: Update golang.org/x/net
+ type: security
+ body: >-
+ Updated golang.org/x/net to address the CVE: CVE-2022-27664.
+
+ - title: Update golang.org/x/text
+ type: security
+ body: >-
+ Updated golang.org/x/text to address the CVE: CVE-2022-32149.
+
+ - title: Update JWT library
+ type: security
+ body: >-
+ Updated our JWT library from https://github.com/dgrijalva/jwt-go to https://github.com/golang-jwt/jwt in order to address spurious complaints about CVE-2020-26160. Edge Stack has never been affected by CVE-2020-26160.
+
+ - title: Fix regression in http to https redirects with AuthService
+ type: bugfix
+ body: >-
+ By default $productName$ adds routes for http to https redirection. When
+ an AuthService is applied in v2.Y of $productName$, Envoy would skip the
+ ext_authz call for non-tls http request and would perform the https
+ redirect. In Envoy 1.20+ the behavior has changed where Envoy will
+ always call the ext_authz filter and must be disabled on a per route
+ basis.
+ This new behavior change introduced a regression in v3.0 of
+ $productName$ when it was upgraded to Envoy 1.22. The http to https
+ redirection no longer works when an AuthService was applied. This fix
+ restores the previous behavior by disabling the ext_authz call on the
+ https redirect routes.
+ github:
+ - title: "#4620"
+ link: https://github.com/emissary-ingress/emissary/issues/4620
+
+ - title: Fix regression in host_redirects with AuthService
+ type: bugfix
+ body: >-
+ When an AuthService is applied in v2.Y of $productName$,
+ Envoy would skip the ext_authz call for all redirect routes and
+ would perform the redirect. In Envoy 1.20+ the behavior has changed
+ where Envoy will always call the ext_authz filter so it must be
+ disabled on a per route basis.
+ This new behavior change introduced a regression in v3.0 of
+ $productName$ when it was upgraded to Envoy 1.22. The host_redirect
+ would call an AuthService prior to redirect if applied. This fix
+ restores the previous behavior by disabling the ext_authz call on the
+ host_redirect routes.
+ github:
+ - title: "#4640"
+ link: https://github.com/emissary-ingress/emissary/issues/4640
+
+ - title: Propagate trace headers to http external filter
+ type: change
+ body: >-
+ Previously, tracing headers were not propagated to an ExternalFilter configured with proto: http. Now, adding supported tracing headers (b3, etc...) to the spec.allowed_request_headers will propagate them to the configured service.
+ docs: topics/using/filters/external/#tracing-header-propagation
+ github:
+ - title: "#3078"
+ link: https://github.com/datawire/apro/issues/3078
+
+ - version: 3.2.0
+ date: '2022-09-27'
+ notes:
+ - title: Update Golang to 1.19.1
+ type: security
+ body: >-
+ Updated Golang to 1.19.1 to address the CVEs: CVE-2022-27664, CVE-2022-32190.
+
+ - title: Add Post Logout Redirect URI support for Oauth2 Filter
+ type: feature
+ body: >-
+ You may now define (on supported IDPs) a postLogoutRedirectURI to your Oauth2 filter.
+ This will allow you to redirect to a specific URI upon logging out. However, in order to achieve this you must
+ define your IDP logout URL to https:{{host}}/.ambassador/oauth2/post-logout-redirect. Upon logout
+ $productName$ will redirect to the custom URI which will then redirect to the URI you have defined in postLogoutRedirectURI.
+ docs: topics/using/filters/oauth2
+
+ - title: Add support for Host resources using secrets from different namespaces
+ type: feature
+ body: >-
+ Previously the Host resource could only use secrets that are in the namespace as the
+ Host. The tlsSecret field in the Host has a new subfield namespace that will allow
+ the use of secrets from different namespaces.
+ docs: topics/running/tls/#bring-your-own-certificate
+
+ - title: Allow bypassing of EDS for manual endpoint insertion
+ type: feature
+ body: >-
+ Set AMBASSADOR_EDS_BYPASS to true to bypass EDS handling of endpoints and have endpoints be
+ inserted to clusters manually. This can help resolve with 503 UH caused by certification rotation relating to
+ a delay between EDS + CDS. The default is false.
+ docs: topics/running/environment/#ambassador_eds_bypass
+
+ - title: Add support for config change batch window before reconfiguring Envoy
+ type: feature
+ body: >-
+ The AMBASSADOR_RECONFIG_MAX_DELAY env var can be optionally set to batch changes for the specified
+ non-negative window period in seconds before doing an Envoy reconfiguration. Default is "1" if not set
+
+ - title: Allow setting custom_tags for traces
+ type: feature
+ body: >-
+ It is now possible to set custom_tags in the
+ TracingService. Trace tags can be set based on
+ literal values, environment variables, or request headers. The existing tag_headers field is now deperacated. If both tag_headers and custom_tags are set then tag_headers will be ignored.
+ (Thanks to Paul!)
+ docs: topics/running/services/tracing-service/
+
+ - title: Add failure_mode_deny option to the RateLimitService
+ type: feature
+ body: >-
+ By default, when Envoy is unable to communicate with the configured
+ RateLimitService then it will allow traffic through. The
+ RateLimitService resource now exposes the
+ failure_mode_deny
+ option. Set failure_mode_deny: true, then Envoy will
+ deny traffic when it is unable to communicate to the RateLimitService
+ returning a 500.
+
+ - title: Change to behavior for associating Hosts with Mappings and Listeners with Hosts
+ type: change
+ body: >-
+ Changes to label matching will change how Hosts are associated with Mappings and how Listeners are associated with Hosts. There was a bug with label
+ selectors that was causing resources that configure a selector to be incorrectly associated with more resources than intended.
+ If any single label from the selector was matched then the resources would be associated.
+ Now it has been updated to correctly only associate these resources if all labels required by
+ their selector are present. This brings the mappingSelector/selector fields in-line with how label selectors are used
+ in Kubernetes. To avoid unexpected behavior after the upgrade, add all labels that Hosts/Listeners have in their
+ mappingSelector/selector to Mappings/Hosts you want to associate with them. You can opt-out of the new behavior
+ by setting the environment variable DISABLE_STRICT_LABEL_SELECTORS to "true" (default: "false").
+ (Thanks to Filip Herceg and Joe Andaverde!).
+ docs: topics/running/environment/#disable_strict_label_selectors
+
+ - title: Envoy upgraded to 1.23.0
+ type: change
+ body: >-
+ The envoy version included in $productName$ has been upgraded from 1.22 to that latest release of 1.23.0. This provides $productName$ with the latest security patches, performances enhancments,and features offered by the envoy proxy.
+ docs: https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.23/v1.23.0
+
+ - title: Properly convert FilterPolicy and ExternalFilter between CRD versions
+ type: bugfix
+ body: >-
+ Previously, $productName$ would incorrectly include empty fields when converting a FilterPolicy or ExternalFilter between versions. This would cause undesired state to be persisted in k8s which would lead to validation issues when trying to kubectl apply the custom resource. This fixes these issues to ensure the correct data is being persisted and roundtripped properly between CRD versions.
+
+ - title: Correctly manage cluster names when service names are very long
+ type: bugfix
+ body: >-
+ Distinct services with names that are the same in the first forty characters will no longer be incorrectly mapped to the same cluster.
+ github:
+ - title: "#4354"
+ link: https://github.com/emissary-ingress/emissary/issues/4354
+
+ - title: Properly populate alt_stats_name for Tracing, Auth and RateLimit Services
+ type: bugfix
+ body: >-
+ Previously, setting the stats_name for the TracingService, RateLimitService
+ or the AuthService would have no affect because it was not being properly passed to the Envoy cluster
+ config. This has been fixed and the alt_stats_name field in the cluster config is now set correctly.
+ (Thanks to Paul!).
+
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : when using the
+ diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not
+ being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing
+ envoy metrics for the diagnostics user interface.
+
+ - title: TCPMappings use correct SNI configuration
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that uses SNI,
+ instead of using the hostname glob in the TCPMapping, uses the hostname glob
+ in the Host that the TLS termination configuration comes from.
+
+ - title: TCPMappings configure TLS termination without a Host resource
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that terminates TLS
+ must have a corresponding Host that it can take the TLS configuration from.
+ This was semi-intentional, but didn't make much sense. You can now use a
+ TLSContext without a Hostas in $productName$ 1.y releases, or a
+ Host with or without a TLSContext as in prior 2.y releases.
+
+ - title: TCPMappings and HTTP Hosts can coexist on Listeners that terminate TLS
+ type: bugfix
+ body: >-
+ Prior releases of $productName$ had the arbitrary limitation that a
+ TCPMapping cannot be used on the same port that HTTP is served on, even if
+ TLS+SNI would make this possible. $productName$ now allows TCPMappings to be
+ used on the same Listener port as HTTP Hosts, as long as that
+ Listener terminates TLS.
+
+ - version: 3.1.0
+ date: '2022-08-01'
+ notes:
+ - title: Add new Filter to support authenticating APIKey's
+ type: feature
+ body: >-
+ A new Filter has been added to support validating APIKey's on incoming requests.The new APIKeyFilter when applied with a FilterPolicy will check to
+ see if the incoming requests has a valid API Key in the request header. $productName$ uses Kubernetes Secret's to lookup valid keys for authorizing requests.
+ docs: topics/using/filters/apikeys
+ - title: Add support to watch for secrets with APIKey's
+ type: feature
+ body: >-
+ Emissary-ingress has been taught to watch for APIKey secrets when $productName$ is running and
+ makes them available to be used with the new APIKeyFilter.
+ - title: A new experimental Redis driver for use with the OAuth2 Filter
+ type: feature
+ body: >-
+ A new opt-in feature flag has been added that allows $productName$ to use a new Redis driver when storing state between requests for the OAuth2 Filter. The new driver has better connection pool handling, shares connections and supports the Redis RESP3 protocol.
+
+ Set AES_REDIS_EXPERIMENTAL_DRIVER_ENABLED=true to enable the experimental feature. Most of the standard Redis configuration fields (e.g.REDIS_*) can be used with the driver.
+ However, due to the drivers better connection handling the new driver no longer supports setting REDIS_SURGE_LIMIT_INTERVAL, REDIS_SURGE_LIMIT_AFTER, REDIS_SURGE_POOL_SIZE, REDIS_SURGE_POOL_DRAIN_INTERVAL and these will be ignored.
+
+ Note: Other $productName$ features such as the RateLimitService will continue to use the current
+ Redis driver and in future releases we plan to roll out the new driver for those features as well.
+ - title: Add support for injecting a valid synthetic RateLimitService
+ type: change
+ body: >-
+ If $productName$ is running then Emissary-ingress ensures that only a single RateLimitService is active. If a user doesn't provide one or provides an invalid one then a synthetic RateLimitService will be
+ injected. If the protocol_version field is not set or set to an invalid value then it will automatically get upgraded protocol_version: v3.
+
+ This matches the existing behavior that was introduced in $productName$ v3.0.0 for the AuthService. For new installs a valid RateLimitService will be added but this
+ change ensures a smooth upgrade from $productName$ to v2.3.Z to v3.Y for users who use the manifest in a GitOps scenario.
+ - title: Add Agent support for OpenAPI 2 contracts
+ type: feature
+ body: >-
+ The agent is now able to parse api contracts using swagger 2, and to convert them to OpenAPI 3, making them available for use in the dev portal.
+ - title: Default YAML enables the diagnostics interface from non-local clients on the admin service port
+ type: change
+ body: >-
+ In the standard published .yaml files, the Module resource enables serving remote client requests to the :8877/ambassador/v0/diag/ endpoint.The associated Helm chart release also now enables it by default.
+ - title: Add additional pprof endpoints
+ type: change
+ body: >-
+ Add pprof endpoints serving additional profiles including CPU profiles (/debug/pprof/profile) and tracing (/debug/pprof/trace). Also add additional endpoints serving the command line running (/debug/pprof/cmdline) and program counters (/debug/pprof/symbol) for the sake of completeness.
+ - title: Correct cookies for mixed HTTP/HTTPS OAuth2 origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter sets cookies for a protectedOrigin, it should set a cookie's "Secure" flag to true for https:// origins and false for http:// origins. However, for filters with multiple origins, it set the cookie's flag based on the first origin listen in the Filter, rather than the origin that the cookie is actually for.
+ - title: Correctly handle refresh tokens for OAuth2 filters with multiple origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter with multiple protectedOrigins needs to adjust the cookies for an active login (which only happens when using a refresh token), it
+ would erroneously redirect the web browser to the last origin listed, rather than returning to the original URL. This has been fixed.
+ - title: Correctly handle CORS and CORs preflight request within the OAuth2 Fitler known endpoints
+ type: bugfix
+ body: >-
+ Previously, the OAuth2 filter's known endpoints /.ambassador/oauth2/logout and /.ambassador/oauth2/multicookie did not understand CORS or CORS preflight request
+ which would cause the browser to reject the request. This has now been fixed and these endpoints will attach the appropriate CORS headers to the response.
+ - title: Fix regression in the agent for the metrics transfer.
+ type: bugfix
+ body: >-
+ A regression was introduced in 2.3.0 causing the agent to miss some of the metrics coming from emissary ingress before sending them to Ambassador cloud. This issue has been resolved to ensure that all the nodes composing the emissary ingress cluster are reporting properly.
+ - title: Handle long cluster names for injected acme-challenge route.
+ type: bugfix
+ body: >-
+ Previously, we would inject an upstream route for acme-challenge that was targeting the localhost auth service cluster. This route is injected to make Envoy configuration happy and the AuthService
+ that is shipped with $productName$ will handle it properly. However, if the cluster name is longer than 60 characters due to a long namespace, etc... then $productName$ will truncate it and make
+ sure it is unique. When this happens the name of the cluster assigned to the acme-challenge route would get out-of-sync and would introduce invalid Envoy configuration.
+
+ To avoid this $productName$ will now inject a route that returns a direct 404 response rather than pointing at an arbitrary cluster. This matches existing behavior and is a transparent
+ change to the user.
+
+ - title: Update Golang to 1.17.12
+ type: security
+ body: >-
+ Updated Golang to 1.17.12 to address the CVEs: CVE-2022-23806, CVE-2022-28327, CVE-2022-24675,
+ CVE-2022-24921, CVE-2022-23772.
+
+ - title: Update Curl to 7.80.0-r2
+ type: security
+ body: >-
+ Updated Curl to 7.80.0-r2 to address the CVEs: CVE-2022-32207, CVE-2022-27782, CVE-2022-27781,
+ CVE-2022-27780.
+
+ - title: Update openSSL-dev to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated openSSL-dev to 1.1.1q-r0 to address CVE-2022-2097.
+
+ - title: Update ncurses to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated ncurses to 1.1.1q-r0 to address CVE-2022-29458
+
+ - title: Upgrade jwt-go
+ type: security
+ body: >-
+ Upgrade jwt-go to latest commit to resolve CVE-2020-26160.
+
+ - version: 3.0.0
+ date: '2022-06-28'
+ notes:
+ - title: Upgrade to Envoy 1.22
+ type: change
+ body: >-
+ $productName$ has been upgraded to Envoy 1.22 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.22.0 Release Notes
+
+ This is a major jump in Envoy versions from the current of 1.17 in EdgeStack 2.X. Most of the changes are under the hood and allow $productName$ to adopt new features in the future. However, one major change that will effect users is the removal of V2 Transport Protocol support. You can find a transition guide here:
+
+ - title: Envoy V2 xDS Transport Protocol Support Removed
+ type: change
+ body: >-
+ Envoy removed support for V2 xDS Transport Protocol which means $productName$ now only supports the Envoy V3 xDS Transport Protocol.
+
+ User should first upgrade to $productName$ 2.3 prior to ensure that the LogServices and External Filters are working properly by setting protocol_version: "v3".
+
+ If protocol_version is not specified in 3.Y, the default value of v2 will cause an error to be posted and a static response will be returned. Therefore, you must set it to protocol_version: v3. If upgrading from a previous version, you will want to set it to v3 and ensure it is working before upgrading to Emissary-ingress 3.Y. The default value for protocol_version remains v2 in the getambassador.io/v3alpha1 CRD specifications to avoid making breaking changes outside of a CRD version change. Future versions of CRD's will deprecate it.
+ docs: topics/using/filters/external
+
+ - title: Initial HTTP/3 Downstream Support
+ type: feature
+ body: >-
+ With the upgrade to Envoy, $productName$ is now able to provide downstream support for HTTP/3. The initial implementation supports exposing http/3 endpoints on port `443`. Future version of $productName$ will seek to provide additional configuration to support more scenarios.
+
+ HTTP/3 uses the Quic protocol over UDP. Changes to your cloud provider provisioned Load Balancer will be required to support UDP traffic if using HTTP/3. External Load Balancers must serve traffic on port 443 because the alt-svc header is not configurable in the initial release of the feature.
+ docs: topics/running/http3
+
+ - version: 2.5.1
+ date: '2022-12-08'
+ notes:
+ - title: Update Golang to 1.19.4
+ type: security
+ body: >-
+ Updated Golang to latest 1.19.4 patch release which contained two CVEs: CVE-2022-41720, CVE-2022-41717.
+
+ CVE-2022-41720 only affects Windows and $productName$ only ships on Linux. CVE-2022-41717 affects HTTP/2 servers that are exposed to external clients. By default, $productName$ exposes the endpoints for DevPortal, Authentication Service, and RateLimitService via Envoy. Envoy enforces a limit on request header size which mitigates the vulnerability.
+
+ - version: 2.5.0
+ date: '2022-11-03'
+ notes:
+ - title: Propagate trace headers to http external filter
+ type: change
+ body: >-
+ Previously, tracing headers were not propagated to an ExternalFilter configured with
+ `proto: http`. Now, adding supported tracing headers (b3, etc...) to the
+ `spec.allowed_request_headers` will propagate them to the configured service.
+ github:
+ - title: "#3078"
+ link: https://github.com/datawire/apro/issues/3078
+
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : then when using the
+ diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not
+ being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing
+ envoy metrics for the diagnostics user interface.
+
+ - title: Bump Golang to 1.19.2
+ type: security
+ body: >-
+ Bump Go from 1.17.12 to 1.19.2. This is to keep the Go version current.
+
+ - version: 2.4.2
+ date: '2022-10-10'
+ notes:
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : then when using the diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing envoy metrics for the diagnostics user interface.
+
+ - title: Backport fixes for handling synthetic auth services
+ type: bugfix
+ body: >-
+ The synthetic AuthService didn't correctly handle AmbassadorID, which was fixed in version 3.1 of $productName$.The fix has been backported to make sure the AuthService is handled correctly during upgrades.
+
+ - version: 2.4.1
+ date: '2022-09-27'
+ notes:
+ - title: Addressing release issue with 2.4.0
+ type: bugfix
+ body: >-
+ During the $productName$ 2.4.0 release process there was an issue with the Emissary binary. This has been patched and resolved.
+
+ - version: 2.4.0
+ date: '2022-09-19'
+ notes:
+ - title: Add support for Host resources using secrets from different namespaces
+ type: feature
+ body: >-
+ Previously the Host resource could only use secrets that are in the namespace as the
+ Host. The tlsSecret field in the Host has a new subfield namespace that will allow
+ the use of secrets from different namespaces.
+ docs: topics/running/tls/#bring-your-own-certificate
+
+ - title: Allow bypassing of EDS for manual endpoint insertion
+ type: change
+ body: >-
+ Set AMBASSADOR_EDS_BYPASS to true to bypass EDS handling of endpoints and have endpoints be
+ inserted to clusters manually. This can help resolve with 503 UH caused by certification rotation relating to
+ a delay between EDS + CDS. The default is false.
+ docs: topics/running/environment/#ambassador_eds_bypass
+
+ - title: Properly convert FilterPolicy and ExternalFilter between CRD versions
+ type: bugfix
+ body: >-
+ Previously, $productName$ would incorrectly include empty fields when converting a FilterPolicy or ExternalFilter between versions. This would cause undesired state to be persisted in k8s which would lead to validation issues when trying to kubectl apply the custom resource. This fixes these issues to ensure the correct data is being persisted and roundtripped properly between CRD versions.
+
+ - title: Properly populate alt_stats_name for Tracing, Auth and RateLimit Services
+ type: bugfix
+ body: >-
+ Previously, setting the stats_name for the TracingService, RateLimitService
+ or the AuthService would have no affect because it was not being properly passed to the Envoy cluster
+ config. This has been fixed and the alt_stats_name field in the cluster config is now set correctly.
+ (Thanks to Paul!)
+
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : when using the
+ diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not
+ being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing
+ envoy metrics for the diagnostics user interface.
+
+ - title: TCPMappings use correct SNI configuration
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that uses SNI,
+ instead of using the hostname glob in the TCPMapping, uses the hostname glob
+ in the Host that the TLS termination configuration comes from.
+
+ - title: TCPMappings configure TLS termination without a Host resource
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that terminates TLS
+ must have a corresponding Host that it can take the TLS configuration from.
+ This was semi-intentional, but didn't make much sense. You can now use a
+ TLSContext without a Hostas in $productName$ 1.y releases, or a
+ Host with or without a TLSContext as in prior 2.y releases.
+
+ - title: TCPMappings and HTTP Hosts can coexist on Listeners that terminate TLS
+ type: bugfix
+ body: >-
+ Prior releases of $productName$ had the arbitrary limitation that a
+ TCPMapping cannot be used on the same port that HTTP is served on, even if
+ TLS+SNI would make this possible. $productName$ now allows TCPMappings to be
+ used on the same Listener port as HTTP Hosts, as long as that
+ Listener terminates TLS.
+
+ - version: 2.3.2
+ date: '2022-08-01'
+ notes:
+ - title: Correct cookies for mixed HTTP/HTTPS OAuth2 origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter sets cookies for a protectedOrigin, it
+ should set a cookie's "Secure" flag to true for https:// origins and false
+ for http:// origins. However, for filters with multiple origins, it set the
+ cookie's flag based on the first origin listen in the Filter, rather than the origin that
+ the cookie is actually for.
+
+ - title: Correctly handle refresh tokens for OAuth2 filters with multiple origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter with multiple protectedOrigins needs to
+ adjust the cookies for an active login (which only happens when using a refresh token), it
+ would erroneously redirect the web browser to the last origin listed, rather than
+ returning to the original URL. This has been fixed.
+
+ - title: Correctly handle CORS and CORs preflight request within the OAuth2 Fitler known endpoints
+ type: bugfix
+ body: >-
+ Previously, the OAuth2 filter's known endpoints /.ambassador/oauth2/logout
+ and /.ambassador/oauth2/multicookie did not understand CORS or CORS preflight request
+ which would cause the browser to reject the request. This has now been fixed and these endpoints will
+ attach the appropriate CORS headers to the response.
+
+ - title: Fix regression in the agent for the metrics transfer.
+ type: bugfix
+ body: >-
+ A regression was introduced in 2.3.0 causing the agent to miss some of the metrics coming from
+ emissary ingress before sending them to Ambassador cloud. This issue has been resolved to ensure
+ that all the nodes composing the emissary ingress cluster are reporting properly.
+
+ - title: Update Golang to 1.17.12
+ type: security
+ body: >-
+ Updated Golang to 1.17.12 to address the CVEs: CVE-2022-23806, CVE-2022-28327, CVE-2022-24675,
+ CVE-2022-24921, CVE-2022-23772.
+
+ - title: Update Curl to 7.80.0-r2
+ type: security
+ body: >-
+ Updated Curl to 7.80.0-r2 to address the CVEs: CVE-2022-32207, CVE-2022-27782, CVE-2022-27781,
+ CVE-2022-27780.
+
+ - title: Update openSSL-dev to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated openSSL-dev to 1.1.1q-r0 to address CVE-2022-2097.
+
+ - title: Update ncurses to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated ncurses to 1.1.1q-r0 to address CVE-2022-29458
+
+ - title: Upgrade jwt-go
+ type: security
+ body: >-
+ Upgrade jwt-go to latest commit to resolve CVE-2020-26160.
+
+ - version: 2.3.1
+ date: '2022-06-10'
+ notes:
+ - title: Fix regression in tracing service config
+ type: bugfix
+ body: >-
+ A regression was introduced in 2.3.0 that leaked zipkin default config fields into the configuration
+ for the other drivers (lightstep, etc...). This caused $productName$ to crash on startup. This issue has been resolved
+ to ensure that the defaults are only applied when driver is zipkin
+ docs: https://github.com/emissary-ingress/emissary/issues/4267
+
+ - title: Envoy security updates
+ type: security
+ body: >-
+ We have backported patches from the Envoy 1.19.5 security update to $productName$'s
+ 1.17-based Envoy, addressing CVE-2022-29224 and CVE-2022-29225. $productName$ is not
+ affected by CVE-2022-29226, CVE-2022-29227, or CVE-2022-29228; as it does not support internal
+ redirects, and does not use Envoy's built-in OAuth2 filter.
+ docs: https://groups.google.com/g/envoy-announce/c/8nP3Kn4jV7k
+
+ - version: 2.3.0
+ date: '2022-06-06'
+ notes:
+ - title: Remove unused packages
+ type: security
+ body: >-
+ Completely remove gdbm, pip, smtplib, and sqlite packages, as they are unused.
+
+ - title: CORS now happens before auth
+ type: bugfix
+ body: >-
+ When CORS is specified (either in a Mapping or in the Ambassador
+ Module), CORS processing will happen before authentication. This corrects a
+ problem where XHR to authenticated endpoints would fail.
+
+ - title: Correctly handle caching of Mappings with the same name in different namespaces
+ type: bugfix
+ body: >-
+ In 2.x releases of $productName$ when there are multiple Mappings that have the same
+ metadata.name across multiple namespaces, their old config would not properly be removed
+ from the cache when their config was updated. This resulted in an inability to update configuration
+ for groups of Mappings that share the same name until the $productName$ pods restarted.
+
+ - title: Fix support for Zipkin API-v1 with Envoy xDS-v3
+ type: bugfix
+ body: >-
+ It is now possible for a TracingService to specify
+ collector_endpoint_version: HTTP_JSON_V1 when using xDS v3 to configure Envoy
+ (which has been the default since $productName$ 1.14.0). The HTTP_JSON_V1
+ value configures Envoy to speak to Zipkin using Zipkin's old API-v1, while the
+ HTTP_JSON value configures Envoy to speak to Zipkin using Zipkin's new
+ API-v2. In previous versions of $productName$ it was only possible to use
+ HTTP_JSON_V1 when explicitly setting the
+ AMBASSADOR_ENVOY_API_VERSION=V2 environment variable to force use of xDS v2
+ to configure Envoy.
+ docs: topics/running/services/tracing-service/
+
+ - title: Added Support for transport protocol v3 in External Filters
+ type: feature
+ body: >-
+ External Filters can now make use of the v3 transport protocol. In addtion to the support for the v3 transport protocol, the default AuthService installed with $productName$ will now only operate with transport protocol v3. In order to support existing External Filters using v2, $productName$ will automatically translate
+ v2 to the new default of v3. Any External Filters will be assumed to be using transport protocol v2 and will use the automatic conversion to v3 unless the new protocol_version field on the External Filter is explicitly set to v3.
+ docs: topics/using/filters/external
+
+ - title: Allow setting propagation modes for Lightstep tracing
+ type: feature
+ body: >-
+ It is now possible to set propagation_modes in the
+ TracingService config when using lightstep as the driver.
+ (Thanks to Paul!)
+ docs: topics/running/services/tracing-service/
+ github:
+ - title: '#4179'
+ link: https://github.com/emissary-ingress/emissary/pull/4179
+
+ - title: Added Support for Certificate Revocation Lists
+ type: feature
+ body: >-
+ $productName$ now supports Envoy's Certificate Revocation lists.
+ This allows users to specify a list of certificates that $productName$ should reject even if the certificate itself is otherwise valid.
+ docs: topics/running/tls
+
+ - title: Added support for the LogService v3 transport protocol
+ type: feature
+ body: >-
+ Previously, a LogService would always have $productName$ communicate with the
+ external log service using the envoy.service.accesslog.v2.AccessLogService
+ API. It is now possible for the LogService to specify
+ protocol_version: v3 to use the newer
+ envoy.service.accesslog.v3.AccessLogService API instead. This functionality
+ is not available if you set the AMBASSADOR_ENVOY_API_VERSION=V2 environment
+ variable.
+ docs: topics/running/services/log-service/
+
+ - title: Improved performance processing OAuth2 Filters
+ type: change
+ body: >-
+ When each OAuth2 Filter that references a Kubernetes secret is loaded, $productName$ previously needed to communicate with the API server to request and validate that secret before loading the next Filter. To improve performance, $productName$ will now load and validate all secrets required by OAuth2 Filters at once prior to loading the filters.
+
+ - title: Deprecated v2 transport protocol for External Filters and AuthServices
+ type: change
+ body: >-
+ A future release of $productName$ will remove support for the now deprecated v2 transport protocol in both AuthServices as well as External Filters. Migrating Existing External Filters from v2 to v3
+ is simple and and example can be found on the External Filter page. This change only impacts gRPC External Filters. HTTP External Filters are unaffected by this change.
+ docs: topics/using/filters/external
+
+ - version: 2.2.2
+ date: '2022-02-25'
+ notes:
+ - title: TLS Secret validation is now opt-in
+ type: change
+ body: >-
+ You may now choose to enable TLS Secret validation by setting the
+ AMBASSADOR_FORCE_SECRET_VALIDATION=true environment variable. The default configuration does not
+ enforce secret validation.
+ docs: topics/running/tls#certificates-and-secrets
+
+ - title: Correctly validate EC (Elliptic Curve) Private Keys
+ type: bugfix
+ body: >-
+ Kubernetes Secrets that should contain an EC (Elliptic Curve) TLS Private Key are now properly validated.
+ github:
+ - title: '#4134'
+ link: https://github.com/emissary-ingress/emissary/issues/4134
+ docs: topics/running/tls#certificates-and-secrets
+
+ - version: 2.2.1
+ date: '2022-02-22'
+ notes:
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Envoy security updates
+ type: security
+ body: >-
+ Upgraded Envoy to address security vulnerabilities CVE-2021-43824, CVE-2021-43825, CVE-2021-43826,
+ CVE-2022-21654, and CVE-2022-21655.
+ docs: https://groups.google.com/g/envoy-announce/c/bIUgEDKHl4g
+ - title: Correctly support canceling rollouts
+ type: bugfix
+ body: >-
+ The Ambassador Agent now correctly supports requests to cancel a rollout.
+ docs: ../../argo/latest/howtos/manage-rollouts-using-cloud
+
+ - version: 2.2.0
+ date: '2022-02-10'
+ notes:
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Ambassador Edge Stack will watch for Cloud Connect Tokens
+ type: change
+ body: >-
+ $productName$ will now watch for ConfigMap or Secret resources specified by the
+ AGENT_CONFIG_RESOURCE_NAME environment variable in order to allow all
+ components (and not only the Ambassador Agent) to authenticate requests to
+ Ambassador Cloud.
+ image: ./v2.2.0-cloud.png
+
+ - title: Update Alpine and libraries
+ type: security
+ body: >-
+ $productName$ has updated Alpine to 3.15, and Python and Go dependencies
+ to their latest compatible versions, to incorporate numerous security patches.
+
+ - title: Support a log-level metric
+ type: feature
+ body: >-
+ $productName$ now supports the metric ambassador_log_level{label="debug"}
+ which will be set to 1 if debug logging is enabled for the running Emissary
+ instance, or to 0 if not. This can help to be sure that a running production
+ instance was not actually left doing debugging logging, for example.
+ (Thanks to Fabrice!)
+ github:
+ - title: '#3906'
+ link: https://github.com/emissary-ingress/emissary/issues/3906
+ docs: topics/running/statistics/8877-metrics/
+
+ - title: Envoy configuration % escaping
+ type: feature
+ body: >-
+ $productName$ is now leveraging a new Envoy Proxy patch that allows Envoy to accept escaped
+ '%' characters in its configuration. This means that error_response_overrides and other
+ custom user content can now contain '%' symbols escaped as '%%'.
+ docs: topics/running/custom-error-responses
+ github:
+ - title: 'DW Envoy: 74'
+ link: https://github.com/datawire/envoy/pull/74
+ - title: 'Upstream Envoy: 19383'
+ link: https://github.com/envoyproxy/envoy/pull/19383
+ image: ./v2.2.0-percent-escape.png
+
+ - title: Stream metrics from Envoy to Ambassador Cloud
+ type: feature
+ body: >-
+ Support for streaming Envoy metrics about the clusters to Ambassador Cloud.
+ github:
+ - title: '#4053'
+ link: https://github.com/emissary-ingress/emissary/pull/4053
+ docs: https://github.com/emissary-ingress/emissary/pull/4053
+
+ - title: Support received commands to pause, continue and abort a Rollout via Agent directives
+ type: feature
+ body: >-
+ The Ambassador agent now receives commands to manipulate Rollouts (pause, continue, and
+ abort are currently supported) via directives and executes them in the cluster. A report
+ is sent to Ambassador Cloud including the command ID, whether it ran successfully, and
+ an error message in case there was any.
+ github:
+ - title: '#4040'
+ link: https://github.com/emissary-ingress/emissary/pull/4040
+ docs: https://github.com/emissary-ingress/emissary/pull/4040
+
+ - title: Validate certificates in TLS Secrets
+ type: bugfix
+ body: >-
+ Kubernetes Secrets that should contain TLS certificates are now validated before being
+ accepted for configuration. A Secret that contains an invalid TLS certificate will be logged
+ as an invalid resource.
+ github:
+ - title: '#3821'
+ link: https://github.com/emissary-ingress/emissary/issues/3821
+ docs: ../topics/running/tls
+ image: ./v2.2.0-tls-cert-validation.png
+
+ - title: Devportal support for using API server definitions from OpenAPI docs
+ type: feature
+ body: >-
+ You can now set preserve_servers in Ambassador Edge Stack's
+ DevPortal resource to configure the DevPortal to use server definitions from
+ the OpenAPI document when displaying connection information for services in the DevPortal.
+ docs: topics/using/dev-portal/
+
+ - version: 2.1.2
+ date: '2022-01-25'
+ notes:
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Docker BuildKit always used for builds
+ type: change
+ body: >-
+ Docker BuildKit is enabled for all Emissary builds. Additionally, the Go
+ build cache is fully enabled when building images, speeding up repeated builds.
+ docs: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md
+
+ - title: Fix OAuth2 Filter jwtAssertion
+ type: bugfix
+ body: >-
+ In $productName$ 2.1.0 and 2.1.1, an OAuth2 Filter with
+ clientAuthentication.method=jwtAssertion would not function correctly as it
+ would fail to select the signing-method-appropriate function to parse the private key.
+ docs: topics/using/filters/oauth2
+ image: ./v2.1.2-filter-jwtassertion.png
+
+ - title: Fix ifRequestHeader without a value
+ type: bugfix
+ body: >-
+ In $productName$ 2.1.0 and 2.1.1, an ifRequestHeader selector (in a
+ FilterPolicy, OAuth2 Filter useSessionCookies, or OAuth2 Filter
+ insteadOfRedirect) without a value or valueRegex
+ would erroneously behave as if valueRegex='^$', rather than performing a
+ simple presence check.
+ docs: custom-resources/getambassador/v3alpha1/filterpolicy
+
+ - title: Fix support for for v2 Mappings with CORS
+ type: bugfix
+ body: >-
+ Ambassador Edge Stack 2.1.1 generated invalid Envoy configuration for
+ getambassador.io/v2Mappings that set
+ spec.cors.origins to a string rather than a list of strings; this has been
+ fixed, and these Mappings should once again function correctly.
+ docs: topics/using/cors/#the-cors-attribute
+ image: ./v2.1.2-mapping-cors.png
+
+ - title: Correctly handle canary Mapping weights when reconfiguring
+ type: bugfix
+ body: >-
+ Changes to the weight of Mapping in a canary group
+ will now always be correctly managed during reconfiguration; such changes could
+ have been missed in earlier releases.
+ docs: topics/using/canary/#the-weight-attribute
+
+ - title: Correctly handle solitary Mappings with explicit weights
+ type: bugfix
+ body: >-
+ A Mapping that is not part of a canary group, but that has a
+ weight less than 100, will be correctly configured to receive all
+ traffic as if the weight were 100.
+ docs: topics/using/canary/#the-weight-attribute
+ image: ./v2.1.2-mapping-less-weighted.png
+
+ - title: Correctly handle empty rewrite in a Mapping
+ type: bugfix
+ body: >-
+ Using rewrite: "" in a Mapping is correctly handled
+ to mean "do not rewrite the path at all".
+ docs: topics/using/rewrites
+ image: ./v2.1.2-mapping-no-rewrite.png
+
+ - title: Correctly use Mappings with host redirects
+ type: bugfix
+ body: >-
+ Any Mapping that uses the host_redirect field is now properly discovered and used. Thanks
+ to Gabriel Féron for contributing this bugfix!
+ github:
+ - title: '#3709'
+ link: https://github.com/emissary-ingress/emissary/issues/3709
+ docs: https://github.com/emissary-ingress/emissary/issues/3709
+
+ - title: Correctly handle DNS wildcards when associating Hosts and Mappings
+ type: bugfix
+ body: >-
+ Mappings with DNS wildcard hostname will now be correctly
+ matched with Hosts. Previously, the case where both the Host
+ and the Mapping use DNS wildcards for their hostnames could sometimes
+ not correctly match when they should have.
+ docs: howtos/configure-communications/
+ image: ./v2.1.2-host-mapping-matching.png
+
+ - title: Fix overriding global settings for adding or removing headers
+ type: bugfix
+ body: >-
+ If the ambassadorModule sets a global default for
+ add_request_headers, add_response_headers,
+ remove_request_headers, or remove_response_headers, it is often
+ desirable to be able to turn off that setting locally for a specific Mapping.
+ For several releases this has not been possible for Mappings that are native
+ Kubernetes resources (as opposed to annotations), as an empty value ("mask the global
+ default") was erroneously considered to be equivalent to unset ("inherit the global
+ default"). This is now fixed.
+ docs: topics/using/defaults/
+
+ - title: Fix empty error_response_override bodies
+ type: bugfix
+ body: >-
+ It is now possible to set a Mapping
+ spec.error_response_overridesbody.text_format to an empty
+ string or body.json_format to an empty dict. Previously, this was possible
+ for annotations but not for native Kubernetes resources.
+ docs: topics/running/custom-error-responses/
+
+ - title: Annotation conversion and validation
+ type: bugfix
+ body: >-
+ Resources that exist as getambassador.io/config annotations rather than as
+ native Kubernetes resources are now validated and internally converted to v3alpha1 and,
+ the same as native Kubernetes resources.
+ image: ./v2.1.2-annotations.png
+
+ - title: Validation error reporting
+ type: bugfix
+ body: >-
+ Resource validation errors are now reported more consistently; it was the case that in
+ some situations a validation error would not be reported.
+
+ - version: 2.1.1
+ date: '2022-01-14'
+ notes:
+ - title: Not recommended; upgrade to 2.1.2 instead
+ type: change
+ isHeadline: true
+ body: >-
+ Ambassador Edge Stack 2.1.1 is not recommended; upgrade to 2.1.2 instead.
+
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Fix discovery of Filters, FilterPolicies, and RateLimits
+ type: bugfix
+ body: >-
+ In Edge Stack 2.1.0, it erroneously ignored Filters,
+ FilterPolicies, and RateLimits that were created as
+ v3alpha1 (but correctly paid attention to them if they were created as
+ v2 or older). This is fixed; it will now correctly pay attention to both API
+ versions.
+ github:
+ - title: '#3982'
+ link: https://github.com/emissary-ingress/emissary/issues/3982
+
+ - version: 2.1.0
+ date: '2021-12-16'
+ notes:
+ - title: Not recommended; upgrade to 2.1.2 instead
+ type: change
+ isHeadline: true
+ body: >-
+ Ambassador Edge Stack 2.1.0 is not recommended; upgrade to 2.1.2 instead.
+
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Smoother migrations with support for getambassador.io/v2 CRDs
+ type: feature
+ body: >-
+ $productName$ supports getambassador.io/v2 CRDs, to simplify migration from $productName$
+ 1.X. Note: it is important to read the migration
+ documentation before starting migration.
+ docs: topics/install/migration-matrix
+ image: ./v2.1.0-smoother-migration.png
+
+ - title: Ambassador Edge Stack CRDs are fully validated
+ type: change
+ body: >-
+ The $productName$ CRDs (Filter, FilterPolicy, and RateLimit)
+ will now be validated for correct syntax by Kubernetes itself. This means that kubectl apply
+ will reject invalid CRDs before they are actually applied, preventing them from causing errors.
+ image: ./v2.1.0-edge-stack-validation.png
+
+ - title: Correctly handle all changing canary configurations
+ type: bugfix
+ body: >-
+ The incremental reconfiguration cache could miss some updates when multiple
+ Mappings had the same prefix ("canary"ing multiple
+ Mappings together). This has been corrected, so that all such
+ updates correctly take effect.
+ github:
+ - title: '#3945'
+ link: https://github.com/emissary-ingress/emissary/issues/3945
+ docs: https://github.com/emissary-ingress/emissary/issues/3945
+ image: ./v2.1.0-canary.png
+
+ - title: Secrets used for ACME private keys will not log errors
+ type: bugfix
+ body: >-
+ When using Kubernetes Secrets to store ACME private keys (as the Edge Stack
+ ACME client does), an error would always be logged about the Secret not being
+ present, even though it was present, and everything was working correctly.
+ This error is no longer logged.
+
+ - title: When using gzip, upstreams will no longer receive encoded data
+ type: bugfix
+ body: >-
+ When using gzip compression, upstream services will no longer receive compressed
+ data. This bug was introduced in 1.14.0. The fix restores the default behavior of
+ not sending compressed data to upstream services.
+ github:
+ - title: '#3818'
+ link: https://github.com/emissary-ingress/emissary/issues/3818
+ docs: https://github.com/emissary-ingress/emissary/issues/3818
+ image: ./v2.1.0-gzip-enabled.png
+
+ - title: Update to busybox 1.34.1
+ type: security
+ body: >-
+ Update to busybox 1.34.1 to resolve CVE-2021-28831, CVE-2021-42378,
+ CVE-2021-42379, CVE-2021-42380, CVE-2021-42381, CVE-2021-42382, CVE-2021-42383,
+ CVE-2021-42384, CVE-2021-42385, and CVE-2021-42386.
+
+ - title: Update Python dependencies
+ type: security
+ body: >-
+ Update Python dependencies to resolve CVE-2020-28493 (jinja2), CVE-2021-28363
+ (urllib3), and CVE-2021-33503 (urllib3).
+
+ - title: Remove test-only code from the built image
+ type: security
+ body: >-
+ Previous built images included some Python packages used only for test. These
+ have now been removed, resolving CVE-2020-29651.
+
+ - version: 2.0.5
+ date: '20211109'
+ notes:
+ - title: More aggressive HTTP cache behavior
+ type: change
+ body: >-
+ When Ambassador Edge Stack makes a cacheable internal request (such as fetching the JWKS
+ endpoint for a JWTFilter), if a cache-miss occurs but a request
+ for that resource is already in-flight, then instead of performing a second request in
+ parallel, it will now wait for the first request to finish and (if the response is
+ cacheable) use that response. If the response turns out to be non-cacheable, then it will
+ proceed to make the second request. This avoids the situation where if a cache entry
+ expires during a moment with high number of concurrent requests, then Edge Stack creates a
+ deluge of concurrent requests to the resource when one aught to have sufficed; this allows
+ the result to be returned more quickly while putting less load on the remote resource.
+ However, if the response turns out to be non-cacheable, then this does effectively
+ serialize requests, increasing the latency for concurrent requests.
+ image: ./v2.0.5-cache-change.png
+
+ - title: AuthService circuit breakers
+ type: feature
+ body: >-
+ It is now possible to set the circuit_breakers for AuthServices,
+ exactly the same as for Mappings and TCPMappings. This makes it
+ possible to configure your AuthService to be able to handle more than 1024
+ concurrent requests.
+ docs: topics/running/services/auth-service/
+ image: ./v2.0.5-auth-circuit-breaker.png
+
+ - title: More accurate durations in the logs
+ type: bugfix
+ body: >-
+ When Ambassador Edge Stack completes an internal request (such as fetching the JWKS
+ endpoint for a JWTFilter) it logs (at the info log
+ level) how long the request took. Previously, the duration logged was how long it took to
+ receive the response header, and did not count the time it takes to receive the entire
+ response body; now it properly times the entire thing. Additionally, it now separately
+ logs the "total duration" and the "networking duration", in order to make it possible to
+ identify when a request was delayed waiting for other requests to finish.
+
+ - title: Improved validity checking for error response overrides
+ type: bugfix
+ body: >-
+ Any token delimited by '%' is now validated agains a whitelist of valid
+ Envoy command operators. Any mapping containing an error_response_overrides
+ section with invalid command operators will be discarded.
+ docs: topics/running/custom-error-responses
+
+ - title: mappingSelector is now correctly supported in the Host CRD
+ type: bugfix
+ body: >-
+ The Host CRD now correctly supports the mappingSelector
+ element, as documented. As a transition aid, selector is a synonym for
+ mappingSelector; a future version of $productName$ will remove the
+ selector element.
+ github:
+ - title: '#3902'
+ link: https://github.com/emissary-ingress/emissary/issues/3902
+ docs: https://github.com/emissary-ingress/emissary/issues/3902
+ image: ./v2.0.5-mappingselector.png
+
+ - version: 2.0.4
+ date: '2021-10-19'
+ notes:
+ - title: General availability!
+ type: feature
+ body: >-
+ We're pleased to introduce $productName$ 2.0.4 for general availability! The
+ 2.X family introduces a number of changes to allow $productName$ to more
+ gracefully handle larger installations, reduce global configuration to better
+ handle multitenant or multiorganizational installations, reduce memory footprint, and
+ improve performance. We welcome feedback!! Join us on
+ Slack and let us know what you think.
+ isHeadline: true
+ docs: about/changes-2.x
+ image: ./edge-stack-GA.png
+
+ - title: API version getambassador.io/v3alpha1
+ type: change
+ body: >-
+ The x.getambassador.io/v3alpha1 API version has become the
+ getambassador.io/v3alpha1 API version. The Ambassador-
+ prefixes from x.getambassador.io/v3alpha1 resources have been
+ removed for ease of migration. Note that getambassador.io/v3alpha1
+ is the only supported API version for 2.0.4 — full support for
+ getambassador.io/v2 will arrive soon in a later 2.X version.
+ docs: about/changes-2.x
+ image: ./v2.0.4-v3alpha1.png
+
+ - title: Support for Kubernetes 1.22
+ type: feature
+ body: >-
+ The getambassador.io/v3alpha1 API version and the published chart
+ and manifests have been updated to support Kubernetes 1.22. Thanks to
+ Mohit Sharma for contributions to
+ this feature!
+ docs: about/changes-2.x
+ image: ./v2.0.4-k8s-1.22.png
+
+ - title: Mappings support configuring strict or logical DNS
+ type: feature
+ body: >-
+ You can now set dns_type between strict_dns and
+ logical_dns in a Mapping to configure the Service
+ Discovery Type.
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+ image: ./v2.0.4-mapping-dns-type.png
+
+ - title: Mappings support controlling DNS refresh with DNS TTL
+ type: feature
+ body: >-
+ You can now set respect_dns_ttl to true to force the
+ DNS refresh rate for a Mapping to be set to the record's TTL
+ obtained from DNS resolution.
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+
+ - title: Support configuring upstream buffer sizes
+ type: feature
+ body: >-
+ You can now set buffer_limit_bytes in the ambassador
+ Module to to change the size of the upstream read and write buffers.
+ The default is 1MiB.
+ docs: topics/running/ambassador/#modify-default-buffer-size
+
+ - title: Version number reported correctly
+ type: bugfix
+ body: >-
+ The release now shows its actual released version number, rather than
+ the internal development version number.
+ github:
+ - title: '#3854'
+ link: https://github.com/emissary-ingress/emissary/issues/3854
+ docs: https://github.com/emissary-ingress/emissary/issues/3854
+ image: ./v2.0.4-version.png
+
+ - title: Large configurations work correctly with Ambassador Cloud
+ type: bugfix
+ body: >-
+ Large configurations no longer cause $productName$ to be unable
+ to communicate with Ambassador Cloud.
+ github:
+ - title: '#3593'
+ link: https://github.com/emissary-ingress/emissary/issues/3593
+ docs: https://github.com/emissary-ingress/emissary/issues/3593
+
+ - title: Listeners correctly support l7Depth
+ type: bugfix
+ body: >-
+ The l7Depth element of the Listener CRD is
+ properly supported.
+ docs: topics/running/listener#l7depth
+ image: ./v2.0.4-l7depth.png
+
+ - version: 2.0.3-ea
+ date: '2021-09-16'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.3 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ isHeadline: true
+ docs: about/changes-2.x
+
+ - title: AES_LOG_LEVEL more widely effective
+ body: The environment variable AES_LOG_LEVEL now also sets the log level for the diagd logger.
+ type: feature
+ docs: topics/running/running/
+ github:
+ - title: '#3686'
+ link: https://github.com/emissary-ingress/emissary/issues/3686
+ - title: '#3666'
+ link: https://github.com/emissary-ingress/emissary/issues/3666
+
+ - title: AmbassadorMapping supports setting the DNS type
+ body: You can now set dns_type in the AmbassadorMapping to configure how Envoy will use the DNS for the service.
+ type: feature
+ docs: topics/using/mappings/#using-dns_type
+
+ - title: Building Emissary no longer requires setting DOCKER_BUILDKIT
+ body: It is no longer necessary to set DOCKER_BUILDKIT=0 when building Emissary. A future change will fully support BuildKit.
+ type: bugfix
+ docs: https://github.com/emissary-ingress/emissary/issues/3707
+ github:
+ - title: '#3707'
+ link: https://github.com/emissary-ingress/emissary/issues/3707
+
+ - version: 2.0.2-ea
+ date: '2021-08-24'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.2 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ isHeadline: true
+ docs: about/changes-2.x
+
+ - title: Envoy security updates
+ type: bugfix
+ body: 'Upgraded envoy to 1.17.4 to address security vulnerabilities CVE-2021-32777, CVE-2021-32778, CVE-2021-32779, and CVE-2021-32781.'
+ docs: https://groups.google.com/g/envoy-announce/c/5xBpsEZZDfE?pli=1
+
+ - title: Expose Envoy's allow_chunked_length HTTPProtocolOption
+ type: feature
+ body: 'You can now set allow_chunked_length in the Ambassador Module to configure the same value in Envoy.'
+ docs: topics/running/ambassador/#content-length-headers
+
+ - title: Envoy-configuration snapshots saved
+ type: change
+ body: Envoy-configuration snapshots get saved (as ambex-#.json) in /ambassador/snapshots. The number of snapshots is controlled by the AMBASSADOR_AMBEX_SNAPSHOT_COUNT environment variable; set it to 0 to disable. The default is 30.
+ docs: topics/running/running/
+
+ - version: 2.0.1-ea
+ date: '2021-08-12'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.1 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ isHeadline: true
+ docs: about/changes-2.x
+
+ - title: Improved Ambassador Cloud visibility
+ type: feature
+ body: Ambassador Agent reports sidecar process information and AmbassadorMapping OpenAPI documentation to Ambassador Cloud to provide more visibility into services and clusters.
+ docs: /docs/cloud/latest/service-catalog/quick-start/
+
+ - title: Configurable per-AmbassadorListener statistics prefix
+ body: The optional stats_prefix element of the AmbassadorListener CRD now determines the prefix of HTTP statistics emitted for a specific AmbassadorListener.
+ type: feature
+ docs: topics/running/listener
+
+ - title: Configurable statistics names
+ body: The optional stats_name element of AmbassadorMapping, AmbassadorTCPMapping, AuthService, LogService, RateLimitService, and TracingService now sets the name under which cluster statistics will be logged. The default is the service, with non-alphanumeric characters replaced by underscores.
+ type: feature
+ docs: topics/running/statistics
+
+ - title: Configurable Dev Portal fetch timeout
+ type: bugfix
+ body: The AmbassadorMapping resource can now specify docs.timeout_ms to set the timeout when the Dev Portal is fetching API specifications.
+ docs: topics/using/dev-portal/
+
+ - title: Dev Portal search strips HTML tags
+ type: bugfix
+ body: The Dev Portal will now strip HTML tags when displaying search results, showing just the actual content of the search result.
+ docs: topics/using/dev-portal/
+
+ - title: Updated klog to reduce log noise
+ type: bugfix
+ body: We have updated to k8s.io/klog/v2 to track upstream and to quiet unnecessary log output.
+ docs: https://github.com/emissary-ingress/emissary/issues/3603
+
+ - title: Subsecond time resolution in logs
+ type: change
+ body: Logs now include subsecond time resolutions, rather than just seconds.
+ docs: https://github.com/emissary-ingress/emissary/pull/3650
+
+ - title: Configurable Envoy-configuration rate limiting
+ type: change
+ body: Set AMBASSADOR_AMBEX_NO_RATELIMIT to true to completely disable ratelimiting Envoy reconfiguration under memory pressure. This can help performance with the endpoint or Consul resolvers, but could make OOMkills more likely with large configurations. The default is false, meaning that the rate limiter is active.
+ docs: topics/concepts/rate-limiting-at-the-edge/
+
+ - title: Improved Consul certificate rotation visibility
+ type: change
+ body: Consul certificate-rotation logging now includes the fingerprints and validity timestamps of certificates being rotated.
+ docs: howtos/consul/#consul-connector-and-encrypted-tls
+
+ - title: Add configurable cache for OIDC replies to the JWT Filter
+ type: feature
+ body: >-
+ The maxStale field is now supported in in the JWT Filter to configure how long $productname$ should cache OIDC responses for similar to the existing maxStale field in the OAuth2 Filter.
+ docs: topics/using/filters/jwt
+
+ - version: 2.0.0-ea
+ date: '2021-06-24'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.0 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ docs: about/changes-2.x
+ isHeadline: true
+
+ - title: Configuration API v3alpha1
+ body: >-
+ $productName$ 2.0.0 introduces API version x.getambassador.io/v3alpha1 for
+ configuration changes that are not backwards compatible with the 1.X family. API versions
+ getambassador.io/v0, getambassador.io/v1, and
+ getambassador.io/v2 are deprecated. Further details are available in the Major Changes
+ in 2.X document.
+ type: feature
+ docs: about/changes-2.x/#1-configuration-api-version-getambassadoriov3alpha1
+ image: ./edge-stack-2.0.0-v3alpha1.png
+
+ - title: The AmbassadorListener Resource
+ body: The new AmbassadorListener CRD defines where and how to listen for requests from the network, and which AmbassadorHost definitions should be used to process those requests. Note that the AmbassadorListener CRD is mandatory and consolidates all port configuration; see the AmbassadorListener documentation for more details.
+ type: feature
+ docs: topics/running/listener
+ image: ./edge-stack-2.0.0-listener.png
+
+ - title: AmbassadorMapping hostname DNS glob support
+ body: >-
+ Where AmbassadorMapping's host field is either an exact match or (with host_regex set) a regex,
+ the new hostname element is always a DNS glob. Use hostname instead of host for best results.
+ docs: about/changes-2.x/#ambassadorhost-and-ambassadormapping-association
+ type: feature
+
+ - title: Memory usage improvements for installations with many AmbassadorHosts
+ body: The behavior of the Ambassador module prune_unreachable_routes field is now automatic, which should reduce Envoy memory requirements for installations with many AmbassadorHosts
+ docs: topics/running/ambassador/#prune-unreachable-routes
+ image: ./edge-stack-2.0.0-prune_routes.png
+ type: feature
+
+ - title: Independent Host actions supported
+ body: Each AmbassadorHost can specify its requestPolicy.insecure.action independently of any other AmbassadorHost, allowing for HTTP routing as flexible as HTTPS routing.
+ docs: topics/running/host-crd/#secure-and-insecure-requests
+ github:
+ - title: '#2888'
+ link: https://github.com/datawire/ambassador/issues/2888
+ image: ./edge-stack-2.0.0-insecure_action_hosts.png
+ type: bugfix
+
+ - title: Correctly set Ingress resource status in all cases
+ body: $productName$ 2.0.0 fixes a regression in detecting the Ambassador Kubernetes service that could cause the wrong IP or hostname to be used in Ingress statuses -- thanks, Noah Fontes!
+ docs: topics/running/ingress-controller
+ type: bugfix
+ image: ./edge-stack-2.0.0-ingressstatus.png
+
+ - title: Stricter mTLS enforcement
+ body: $productName$ 2.0.0 fixes a bug where mTLS could use the wrong configuration when SNI and the :authority header didn't match
+ type: bugfix
+
+ - title: Port configuration outside AmbassadorListener has been moved to AmbassadorListener
+ body: The TLSContextredirect_cleartext_from and AmbassadorHostrequestPolicy.insecure.additionalPort elements are no longer supported. Use a AmbassadorListener for this functionality instead.
+ type: change
+ docs: about/changes-2.x/#tlscontext-redirect_cleartext_from-and-host-insecureadditionalport
+
+ - title: PROXY protocol configuration has been moved to AmbassadorListener
+ body: The use_proxy_protocol element of the Ambassador Module is no longer supported, as it is now part of the AmbassadorListener resource (and can be set per-AmbassadorListener rather than globally).
+ type: change
+ docs: about/changes-2.x/#proxy-protocol-configuration
+
+ - title: Stricter rules for AmbassadorHost/AmbassadorMapping association
+ body: An AmbassadorMapping will only be matched with an AmbassadorHost if the AmbassadorMapping's host or the AmbassadorHost's selector (or both) are explicitly set, and match. This change can significantly improve $productName$'s memory footprint when many AmbassadorHosts are involved. Further details are available in the 2.0.0 Changes document.
+ docs: about/changes-2.x/#host-and-mapping-association
+ type: change
+
+ - title: AmbassadorHost or Ingress now required for TLS termination
+ body: An AmbassadorHost or Ingress resource is now required when terminating TLS -- simply creating a TLSContext is not sufficient. Further details are available in the AmbassadorHost CRD documentation.
+ docs: about/changes-2.x/#host-tlscontext-and-tls-termination
+ type: change
+ image: ./edge-stack-2.0.0-host_crd.png
+
+ - title: Envoy V3 APIs
+ body: By default, $productName$ will configure Envoy using the V3 Envoy API. This change is mostly transparent to users, but note that Envoy V3 does not support unsafe regular expressions or, e.g., Zipkin's V1 collector protocol. Further details are available in the Major Changes in 2.X document.
+ type: change
+ docs: about/changes-2.x/#envoy-v3-api-by-default
+
+ - title: Module-based TLS no longer supported
+ body: The tls module and the tls field in the Ambassador module are no longer supported. Please use TLSContext resources instead.
+ docs: about/changes-2.x/#tls-the-ambassador-module-and-the-tls-module
+ image: ./edge-stack-2.0.0-tlscontext.png
+ type: change
+
+ - title: Higher performance while generating Envoy configuration now enabled by default
+ body: The environment variable AMBASSADOR_FAST_RECONFIGURE is now set by default, enabling the higher-performance implementation of the code that $productName$ uses to generate and validate Envoy configurations.
+ docs: topics/running/scaling/#ambassador_fast_reconfigure-and-ambassador_legacy_mode-flags
+ type: change
+
+ - title: Service Preview no longer supported
+ body: >-
+ Service Preview and the AGENT_SERVICE environment variable are no longer supported.
+ The Telepresence product replaces this functionality.
+ docs: https://www.getambassador.io/docs/telepresence/
+ type: change
+
+ - title: edgectl no longer supported
+ body: The edgectl CLI tool has been deprecated; please use the emissary-ingress helm chart instead.
+ docs: topics/install/helm/
+ type: change
+
+ - version: 1.14.2
+ date: '2021-09-29'
+ notes:
+ - title: Mappings support controlling DNS refresh with DNS TTL
+ type: feature
+ body: >-
+ You can now set respect_dns_ttl in Ambassador Mappings. When true it
+ configures that upstream's refresh rate to be set to resource record’s TTL
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+
+ - title: Mappings support configuring strict or logical DNS
+ type: feature
+ body: >-
+ You can now set dns_type in Ambassador Mappings to use Envoy's
+ logical_dns resolution instead of the default strict_dns.
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+
+ - title: Support configuring upstream buffer size
+ type: feature
+ body: >-
+ You can now set buffer_limit_bytes in the ambassador
+ Module to to change the size of the upstream read and write buffers.
+ The default is 1MiB.
+ docs: topics/running/ambassador/#modify-default-buffer-size
+
+ - version: 1.14.1
+ date: '2021-08-24'
+ notes:
+ - title: Envoy security updates
+ type: change
+ body: >-
+ Upgraded Envoy to 1.17.4 to address security vulnerabilities CVE-2021-32777,
+ CVE-2021-32778, CVE-2021-32779, and CVE-2021-32781.
+ docs: https://groups.google.com/g/envoy-announce/c/5xBpsEZZDfE
+
+ - version: 1.14.0
+ date: '2021-08-19'
+ notes:
+ - title: Envoy upgraded to 1.17.3!
+ type: change
+ body: >-
+ Update from Envoy 1.15 to 1.17.3
+ docs: https://www.envoyproxy.io/docs/envoy/latest/version_history/version_history
+
+ - title: Expose Envoy's allow_chunked_length HTTPProtocolOption
+ type: feature
+ body: >-
+ You can now set allow_chunked_length in the Ambassador Module to configure
+ the same value in Envoy.
+ docs: topics/running/ambassador/#content-length-headers
+
+ - title: Default Envoy API version is now V3
+ type: change
+ body: >-
+ AMBASSADOR_ENVOY_API_VERSION now defaults to V3
+ docs: topics/running/running/#ambassador_envoy_api_version
+
+ - title: Subsecond time resolution in logs
+ type: change
+ body: Logs now include subsecond time resolutions, rather than just seconds.
+ docs: https://github.com/emissary-ingress/emissary/pull/3650
+
+ - version: 1.13.10
+ date: '2021-07-28'
+ notes:
+ - title: Fix for CORS origins configuration on the Mapping resource
+ type: bugfix
+ body: >-
+ Fixed a regression when specifying a comma separated string for cors.origins
+ on the Mapping resource.
+ ([#3609](https://github.com/emissary-ingress/emissary/issues/3609))
+ docs: topics/using/cors
+ image: ../images/emissary-1.13.10-cors-origin.png
+
+ - title: New Envoy-configuration snapshots for debugging
+ body: 'Envoy-configuration snapshots get saved (as ambex-#.json) in /ambassador/snapshots. The number of snapshots is controlled by the AMBASSADOR_AMBEX_SNAPSHOT_COUNT environment variable; set it to 0 to disable. The default is 30.'
+ type: change
+ docs: topics/running/environment/
+
+ - title: Optionally remove ratelimiting for Envoy reconfiguration
+ body: >-
+ Set AMBASSADOR_AMBEX_NO_RATELIMIT to true to completely disable
+ ratelimiting Envoy reconfiguration under memory pressure. This can help performance with
+ the endpoint or Consul resolvers, but could make OOMkills more likely with large
+ configurations. The default is false, meaning that the rate limiter is
+ active.
+ type: change
+ docs: topics/running/environment/
+
+ - title: Mappings support configuring the DevPortal fetch timeout
+ type: bugfix
+ body: >-
+ The Mapping resource can now specify docs.timeout_ms to set the
+ timeout when the Dev Portal is fetching API specifications.
+ docs: topics/using/dev-portal
+ image: ../images/edge-stack-1.13.10-docs-timeout.png
+
+ - title: Dev Portal will strip HTML tags when displaying results
+ type: bugfix
+ body: >-
+ The Dev Portal will now strip HTML tags when displaying search results, showing just the
+ actual content of the search result.
+ docs: topics/using/dev-portal
+
+ - title: Consul certificate rotation logs more information
+ type: change
+ body: >-
+ Consul certificate-rotation logging now includes the fingerprints and validity timestamps
+ of certificates being rotated.
+ docs: howtos/consul/
+ image: ../images/edge-stack-1.13.10-consul-cert-log.png
+
+ - version: 1.13.9
+ date: '2021-06-30'
+ notes:
+ - title: Fix for TCPMappings
+ body: >-
+ Configuring multiple TCPMappings with the same ports (but different hosts) no longer
+ generates invalid Envoy configuration.
+ type: bugfix
+ docs: topics/using/tcpmappings/
+
+ - version: 1.13.8
+ date: '2021-06-08'
+ notes:
+ - title: Fix Ambassador Cloud Service Details
+ body: >-
+ Ambassador Agent now accurately reports up-to-date Endpoint information to Ambassador
+ Cloud
+ type: bugfix
+ docs: tutorials/getting-started/#3-connect-your-cluster-to-ambassador-cloud
+ image: ../images/edge-stack-1.13.8-cloud-bugfix.png
+
+ - title: Improved Argo Rollouts Experience with Ambassador Cloud
+ body: >-
+ Ambassador Agent reports ConfigMaps and Deployments to Ambassador Cloud to provide a
+ better Argo Rollouts experience. See [Argo+Ambassador
+ documentation](https://www.getambassador.io/docs/argo) for more info.
+ type: feature
+ docs: https://www.getambassador.io/docs/argo
+
+ - version: 1.13.7
+ date: '2021-06-03'
+ notes:
+ - title: JSON logging support
+ body: >-
+ Add AMBASSADOR_JSON_LOGGING to enable JSON for most of the Ambassador control plane. Some
+ (but few) logs from gunicorn and the Kubernetes client-go package still log text.
+ image: ../images/edge-stack-1.13.7-json-logging.png
+ docs: topics/running/running/#log-format
+ type: feature
+
+ - title: Consul resolver bugfix with TCPMappings
+ body: >-
+ Fixed a bug where the Consul resolver would not actually use Consul endpoints with
+ TCPMappings.
+ image: ../images/edge-stack-1.13.7-tcpmapping-consul.png
+ docs: topics/running/resolvers/#the-consul-resolver
+ type: bugfix
+
+ - title: Memory usage calculation improvements
+ body: >-
+ Ambassador now calculates its own memory usage in a way that is more similar to how the
+ kernel OOMKiller tracks memory.
+ image: ../images/edge-stack-1.13.7-memory.png
+ docs: topics/running/scaling/#inspecting-ambassador-performance
+ type: change
+
+ - version: 1.13.6
+ date: '2021-05-24'
+ notes:
+ - title: Quieter logs in legacy mode
+ type: bugfix
+ body: >-
+ Fixed a regression where Ambassador snapshot data was logged at the INFO label
+ when using AMBASSADOR_LEGACY_MODE=true.
+
+ - version: 1.13.5
+ date: '2021-05-13'
+ notes:
+ - title: Correctly support proper_case and preserve_external_request_id
+ type: bugfix
+ body: >-
+ Fix a regression from 1.8.0 that prevented ambassadorModule
+ config keys proper_case and preserve_external_request_id
+ from working correctly.
+ docs: topics/running/ambassador/#header-case
+
+ - title: Correctly support Ingress statuses in all cases
+ type: bugfix
+ body: >-
+ Fixed a regression in detecting the Ambassador Kubernetes service that could cause the
+ wrong IP or hostname to be used in Ingress statuses (thanks, [Noah
+ Fontes](https://github.com/impl)!
+ docs: topics/running/ingress-controller
+
+ - version: 1.13.4
+ date: '2021-05-11'
+ notes:
+ - title: Envoy 1.15.5
+ body: >-
+ Incorporate the Envoy 1.15.5 security update by adding the
+ reject_requests_with_escaped_slashes option to the Ambassador module.
+ image: ../images/edge-stack-1.13.4.png
+ docs: topics/running/ambassador/#rejecting-client-requests-with-escaped-slashes
+ type: security
+# Don't go any further back than 1.13.4.
diff --git a/docs/edge-stack/3.9/topics/install/helm.md b/docs/edge-stack/3.9/topics/install/helm.md
new file mode 100644
index 000000000..84c40d586
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/helm.md
@@ -0,0 +1,107 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Install with Helm
+
+
+
+ To migrate from $productName$ 1.X to $productName$ 2.X, see the
+ [$productName$ migration matrix](../migration-matrix/). This guide
+ **will not work** for that, due to changes to the configuration
+ resources used for $productName$ 2.X.
+
+
+
+[Helm](https://helm.sh) is a package manager for Kubernetes that automates the release and management of software on Kubernetes. $productName$ can be installed via a Helm chart with a few simple steps, depending on if you are deploying for the first time, upgrading $productName$ from an existing installation, or migrating from $productName$.
+
+## Before you begin
+
+
+ $productName$ requires a valid license or cloud connect token to start. You can refer to the quickstart guide
+ for instructions on how to obtain a free community license and connect your installation to Ambassador cloud.
+
+
+The $productName$ Helm chart is hosted by Datawire and published at `https://app.getambassador.io`.
+
+Start by adding this repo to your helm client with the following command:
+
+```bash
+helm repo add datawire https://app.getambassador.io
+helm repo update
+```
+
+## Install with Helm
+
+When you run the Helm chart, it installs $productName$.
+
+1. Install the $productName$ CRDs.
+
+ Before installing $productName$ $version$ itself, you must configure your
+ Kubernetes cluster to support the `getambassador.io/v3alpha1` and `getambassador.io/v2`
+ configuration resources. This is required.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. Install the $productName$ Chart with the following command:
+
+ ```
+ helm install -n $productNamespace$ --create-namespace \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+3. Next Steps
+
+ $productName$ should now be successfully installed and running, but in order to get started deploying Services and test routing to them you need to configure a few more resources.
+
+ - [The `Listener` Resource](../../running/listener/) is required to configure which ports the $productName$ pods listen on so that they can begin responding to requests.
+ - [The `Mapping` Resouce](../../using/intro-mappings/) is used to configure routing requests to services in your cluster.
+ - [The `Host` Resource](../../running/host-crd/) configures TLS termination for enablin HTTPS communication.
+ - Explore how $productName$ [configures communication with clients](../../../howtos/configure-communications)
+
+
+ We strongly recommend following along with our Quickstart Guide to get started by creating a Listener, deploying a simple service to test with, and setting up a Mapping to route requests from $productName$ to the demo service.
+
+
+
+ $productName$ $version$ includes a Deployment in the $productNamespace$ namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+For more advanced configuration and details about helm values,
+[please see the helm chart.](https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$)
+
+## Upgrading an existing installation
+
+See the [migration matrix](../migration-matrix) for instructions about upgrading
+$productName$.
diff --git a/docs/edge-stack/3.9/topics/install/index.less b/docs/edge-stack/3.9/topics/install/index.less
new file mode 100644
index 000000000..bc649e7ca
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/index.less
@@ -0,0 +1,57 @@
+@media (max-width: 769px) {
+ #index-installContainer {
+ flex-direction: column;
+ }
+ .index-dropdown {
+ width: auto;
+ }
+ .index-dropBtn {
+ width: 100%;
+ }
+}
+
+.index-dropBtn {
+ background-color: #8e77ff;
+ color: white;
+ padding: 10px;
+ font-size: 16px;
+ border: none;
+ margin-top: -20px;
+}
+
+.index-dropdown {
+ position: relative;
+ display: inline-block;
+}
+
+.index-dropdownContent {
+ display: none;
+ position: absolute;
+ background-color: #f1f1f1;
+ width: 100%;
+ box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+ z-index: 1;
+}
+
+.index-dropdownContent a {
+ color: black;
+ padding: 12px 16px;
+ text-decoration: none;
+ display: block;
+}
+
+.index-dropdownContent a:hover {
+ background-color: #ddd;
+}
+
+.index-dropdown:hover .index-dropdownContent {
+ display: block;
+}
+
+.index-dropdown:hover .index-dropBtn {
+ background-color: #5f3eff;
+}
+
+#index-installContainer {
+ display: flex;
+}
diff --git a/docs/edge-stack/3.9/topics/install/index.md b/docs/edge-stack/3.9/topics/install/index.md
new file mode 100644
index 000000000..ac6a79d41
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/index.md
@@ -0,0 +1,40 @@
+import Alert from '@material-ui/lab/Alert';
+import './index.less'
+
+# Installing $productName$
+
+## Install with Helm
+Helm, the package manager for Kubernetes, is the recommended way to install
+$productName$. Full details are in the [Helm instructions.](helm/)
+
+## Install with Kubernetes YAML
+Another way to install $productName$ if you are unable to use Helm is to
+directly apply Kubernetes YAML. See details in the
+[manual YAML installation instructions.](yaml-install).
+
+## Try the demo with Docker
+The Docker install will let you try the $productName$ locally in seconds,
+but is not supported for production workloads. [Try $productName$ on Docker.](docker/)
+
+## Upgrade or migrate to a newer version
+If you already have an existing installation of $AESproductName$ or
+$OSSproductName$, you can upgrade your instance. The [migration matrix](migration-matrix/)
+shows you how.
+
+## Container Images
+Although our installation guides will favor using the `docker.io` container registry,
+we publish $AESproductName$ and $OSSproductName$ releases to multiple registries.
+
+Starting with version 1.0.0, you can pull the aes image from any of the following registries:
+- `docker.io/datawire/`
+- `gcr.io/datawire/`
+
+We want to give you flexibility and independence from a hosting platform's uptime to support
+your production needs for $AESproductName$ or $OSSproductName$. Read more about
+[Running $productName$ in Production](../running).
+
+# What’s Next?
+$productName$ has a comprehensive range of [features](/features/) to
+support the requirements of any edge microservice. To learn more about how $productName$ works, along with use cases, best practices, and more,
+check out the [Welcome page](../../tutorials/getting-started/) or read the [$productName$
+Story](../../about/why-ambassador).
diff --git a/docs/edge-stack/3.9/topics/install/migrate-to-3-alternate.md b/docs/edge-stack/3.9/topics/install/migrate-to-3-alternate.md
new file mode 100644
index 000000000..d0b791a12
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/migrate-to-3-alternate.md
@@ -0,0 +1,36 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrading $productName$ with a separate cluster
+
+You can upgrade from any version of $AESproductName$ or $OSSproductName$ to
+any version of either by installing the new version in a new Kubernetes cluster,
+then copying over configuration as needed. This is the way to be absolutely
+certain that each installation cannot affect the other: it is extremely safe,
+but is also significantly more effort.
+
+For example, to upgrade from some other version of $AESproductName$ or
+$OSSproductName$ to $productName$ $version$:
+
+1. Install $productName$ $version$ in a completely new cluster.
+
+2. **Create `Listener`s for $productName$ $version$.**
+
+ When $productName$ $version$ starts, it will not have any `Listener`s, and it will not
+ create any. You must create `Listener` resources by hand, or $productName$ $version$
+ will not listen on any ports.
+
+3. Copy the entire configuration from the $productName$ 1.X cluster to the $productName$
+ $version$ cluster. This is most simply done with `kubectl get -o yaml | kubectl apply -f -`.
+
+ This will create `getambassador.io/v2` resources in the $productName$ $version$ cluster.
+ $productName$ $version$ will translate them internally to `getambassador.io/v3alpha1`
+ resources.
+
+4. Each $productName$ instance has its own cluster, so you can test the new
+ instance without disrupting traffic to the existing instance.
+
+5. If you need to make changes, you can change the `getambassador.io/v2` resource, or convert the
+ resource you're changing to `getambassador.io/v3alpha1` by using `kubectl edit`.
+
+6. Once everything is working with both versions, transfer incoming traffic to the $productName$
+ $version$ cluster.
diff --git a/docs/edge-stack/3.9/topics/install/migration-matrix.md b/docs/edge-stack/3.9/topics/install/migration-matrix.md
new file mode 100644
index 000000000..ead43c6e0
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/migration-matrix.md
@@ -0,0 +1,48 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrading $productName$
+
+
+ Read the instructions below before making any changes to your cluster!
+
+
+There are currently multiple paths for upgrading $productName$, depending on what version you're currently
+running, what you want to be running, and whether you installed $productName$ using [Helm](../helm) or
+YAML.
+
+(To check out if you installed $productName$ using Helm, run `helm list --all` and see if
+$productName$ is listed. If so, you installed using Helm.)
+
+
+ Read the instructions below before making any changes to your cluster!
+
+
+## If you are currently running $OSSproductName$
+
+See the [instructions on updating $OSSproductName$](../../../../../emissary/$ossDocsVersion$/topics/install/migration-matrix).
+
+## If you installed $productName$ using Helm
+
+| If you're running. | You can upgrade to |
+|-----------------------------------------|----------------------------------------------------------------------------------|
+| $AESproductName$ 3.8.X | [$AESproductName$ $version$](../upgrade/helm/edge-stack-3.8/edge-stack-3.X) |
+| $AESproductName$ 3.7.X | [$AESproductName$ $version$](../upgrade/helm/edge-stack-3.7/edge-stack-3.X) |
+| $AESproductName$ $versionTwoX$ | [$AESproductName$ $version$](../upgrade/helm/edge-stack-2.5/edge-stack-3.X) |
+| $AESproductName$ 2.4.X | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-2.4/edge-stack-2.X) |
+| $AESproductName$ 2.0.X | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-2.0/edge-stack-2.X) |
+| $AESproductName$ $versionOneX$ | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-1.14/edge-stack-2.X) |
+| $AESproductName$ prior to $versionOneX$ | [$AESproductName$ $versionOneX$](../../../../1.14/topics/install/upgrading) |
+| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/helm/emissary-3.9/edge-stack-3.X) |
+
+## If you installed $AESproductName$ manually by applying YAML
+
+| If you're running. | You can upgrade to |
+|-----------------------------------------|----------------------------------------------------------------------------------|
+| $AESproductName$ 3.8.X | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-3.8/edge-stack-3.X) |
+| $AESproductName$ 3.7.X | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-3.7/edge-stack-3.X) |
+| $AESproductName$ $versionTwoX$ | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-2.5/edge-stack-3.X) |
+| $AESproductName$ 2.4.X | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-2.4/edge-stack-2.X) |
+| $AESproductName$ 2.0.X | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-2.0/edge-stack-2.X) |
+| $AESproductName$ $versionOneX$ | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-1.14/edge-stack-2.X) |
+| $AESproductName$ prior to $versionOneX$ | [$AESproductName$ $versionOneX$](../../../../1.14/topics/install/upgrading) |
+| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/yaml/emissary-3.9/edge-stack-3.X) |
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-1.14/edge-stack-2.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-1.14/edge-stack-2.X.md
new file mode 100644
index 000000000..88983d794
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-1.14/edge-stack-2.X.md
@@ -0,0 +1,378 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 1.14.X to $productName$ $versionTwoX$ (Helm)
+
+
+ This guide covers migrating from $productName$ 1.14.X to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation originally made using Helm.
+ If you did not install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+We're pleased to introduce $productName$ $versionTwoX$! The 2.X family introduces a number of
+changes to allow $productName$ to more gracefully handle larger installations (including
+multitenant or multiorganizational installations), reduce memory footprint, and improve
+performance. In keeping with [SemVer](https://semver.org), $productName$ 2.X introduces
+some changes that aren't backward-compatible with 1.X. These changes are detailed in
+[Major Changes in $productName$ 2.X](../../../../../../about/changes-2.x).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $productName$ 1.14 and $productName$
+$versionTwoX$ side-by-side in the same cluster. This gives $productName$ $versionTwoX$
+and $productName$ 1.14 access to all the same configuration resources, with some
+important caveats:
+
+1. **$productName$ 1.14 will not see any `getambassador.io/v3alpha1` resources.**
+
+ This is intentional; it provides a way to apply configuration only to
+ $productName$ $versionTwoX$, while not interfering with the operation of your
+ $productName$ 1.14 installation.
+
+2. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $productName$ $versionTwoX$ installation from
+ seeing a particular bit of $productName$ 1.14 configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $productName$ $versionTwoX$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $productName$ $versionTwoX$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+3. **$productName$ 1.14 must remain in control of ACME while both installations are running.**
+
+ The processes that handle ACME challenges cannot be managed by both $productName$
+ 1.X and $productName$ $versionTwoX$ at the same time. The instructions below disable ACME
+ in $productName$ $versionTwoX$, allowing $productName$ 1.14 to continue managing it.
+
+ This implies that any new `Host`s used for $productName$ 1.14 should be created using
+ `getambassador.io/v2` so that $productName$ 1.14 can see them.
+
+4. **Check `AuthService` and `RateLimitService` resources, if any.**
+
+ If you have an [`AuthService`](../../../../../using/authservice/) or
+ [`RateLimitService`](../../../../../running/services/rate-limit-service) installed, make
+ sure that they are using the [namespace-qualified DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services).
+ If they are not, the initial migration tests may fail.
+
+ Additionally, when installing with Helm, you must make sure that $productName$ $versionTwoX$
+ does not attempt to create duplicate `AuthService` and `RateLimitService` entries. Add
+
+ ```
+ --set rateLimit.create=false
+ ```
+
+ and
+
+ ```
+ --set authService.create=false
+ ```
+
+ on the Helm command line to prevent duplicating these resources.
+
+5. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The best way to avoid multiple agents when installing with Helm is to use
+ `--set emissary-ingress.agent.enabled=false` to tell Helm not to install a
+ new Agent with $productName$ $versionTwoX$. Once testing is done, you can switch
+ Agents safely.
+
+6. **If you use ACME for multiple `Host`s, add a wildcard `Host` too.**
+
+ This is required to manage a known issue. This issue will be resolved in a future
+ $AESproductName$ release.
+
+7. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $productName$ 1.14 that use selectors that will match
+ Pods from $productName$ $versionTwoX$, traffic will be erroneously split between
+ $productName$ 1.14 and $productName$ $versionTwoX$. The labels used by $productName$
+ $versionTwoX$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $productName$ $versionTwoX$ in a separate cluster](../../../../migrate-to-2-alternate).
+This permits absolute certainty that your $productName$ 1.14 configuration will not be
+affected by changes meant for $productName$ $versionTwoX$, and it eliminates concerns about
+ACME, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is an eight-step process:
+
+1. **Make sure that older configuration resources are not present.**
+
+ $productName$ 2.X does not support `getambassador.io/v0` or `getambassador.io/v1`
+ resources, and Kubernetes will not permit removing support for CRD versions that are
+ still in use for stored resources. To verify that no resources older than
+ `getambassador.io/v2` are active, run
+
+ ```
+ kubectl get crds -o 'go-template={{range .items}}{{.metadata.name}}={{.status.storedVersions}}{{"\n"}}{{end}}' | fgrep getambassador.io
+ ```
+
+ If `v1` is present in the output, **do not begin migration.** The old resources must be
+ converted to `getambassador.io/v2` and the `storedVersion` information in the cluster
+ must be updated. If necessary, contact Ambassador Labs on [Slack](http://a8r.io/slack)
+ for more information.
+
+2. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you must configure your
+ Kubernetes cluster to support its new `getambassador.io/v3alpha1` configuration
+ resources. Note that `getambassador.io/v2` resources are still supported, but **you
+ must install support for `getambassador.io/v3alpha1`** to run $productName$ $versionTwoX$,
+ even if you intend to continue using only `getambassador.io/v2` resources for some
+ time.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, you need to install $productName$ $versionTwoX$ itself
+ **in the same namespace as your existing $productName$ 1.14 installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+
+ Make sure that you set the AES_ACME_LEADER_DISABLE flag. This prevents
+ $productName$ $versionTwoX$ from trying to manage ACME, so that $productName$ 1.14 can
+ do it instead.
+
+
+ Start by making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Typically, $productName$ 1.14 was installed in the `ambassador` namespace. If you installed
+ $productName$ 1.14 in a different namespace, change the namespace in the commands below.
+
+ - If you do not need to set `AMBASSADOR_LABEL_SELECTOR`:
+
+ ```bash
+ helm install -n ambassador \
+ --set emissary-ingress.agent.enabled=false \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE=true \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ```
+
+ - If you do need to set `AMBASSADOR_LABEL_SELECTOR`, use `--set`, for example:
+
+ ```bash
+ helm install -n ambassador \
+ --set emissary-ingress.agent.enabled=false \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE=true \
+ --set emissary-ingress.env.AMBASSADOR_LABEL_SELECTOR="version-two=true" \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart for $productName$ $versionTwoX$.
+ Do not use the ambassador Helm chart.
+
+
+4. **Install `Listener`s and `Host`s as needed.**
+
+ An important difference between $productName$ 1.14 and $productName$ $versionTwoX$ is the
+ new **mandatory** `Listener` CRD. Also, when running both installations side by side,
+ you will need to make sure that a `Host` is present for the new $productName$ $versionTwoX$
+ Service. For example:
+
+ ```bash
+ kubectl apply -f - <
+ Make sure that any Hosts you create use API version getambassador.io/v2,
+ so that they can be managed by $productName$ 1.14 as long as both installations are running.
+
+
+ This example requires that you know the hostname for the $productName$ Service (`$EMISSARY_HOSTNAME`)
+ and that you have created a TLS Secret for it in `$EMISSARY_TLS_SECRET`.
+
+5. **Test!**
+
+ Your $productName$ $versionTwoX$ installation can support the `getambassador.io/v2`
+ configuration resources used by $productName$ 1.14, but you may need to make some
+ changes to the configuration, as detailed in the documentation on
+ [configuring $productName$ Communications](../../../../../../howtos/configure-communications)
+ and [updating CRDs to `getambassador.io/v3alpha1`](../../../../convert-to-v3alpha1).
+
+
+ Kubernetes will not allow you to have a getambassador.io/v3alpha1 resource
+ with the same name as a getambassador.io/v2 resource or vice versa: only
+ one version can be stored at a time.
+
+ If you find that your $productName$ $versionTwoX$ installation and your $productName$ 1.14
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 2, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your 1.14 CRDs, delete your
+ installation of $productName$ $versionTwoX$, and delete the `emissary-system` namespace.
+
+6. **When ready, switch over to $productName$ $versionTwoX$.**
+
+ You can run $productName$ 1.14 and $productName$ $versionTwoX$ side-by-side as long as you care
+ to. However, taking full advantage of $productName$ 2.X's capabilities **requires**
+ [updating your configuration to use `getambassador.io/v3alpha1` configuration resources](../../../../convert-to-v3alpha1),
+ since some useful features in $productName$ $versionTwoX$ are only available using
+ `getambassador.io/v3alpha1` resources.
+
+ When you're ready to have $productName$ $versionTwoX$ handle traffic on its own, switch
+ your original $productName$ 1.14 Service to point to $productName$ $versionTwoX$. Use
+ `kubectl edit service ambassador` and change the `selectors` to:
+
+ ```
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+7. **Install the $productName$ $versionTwoX$ Ambassador Agent.**
+
+ First, scale the 1.14 agent to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent. **Note that if you needed to set
+ `AMBASSADOR_LABEL_SELECTOR`, you must add that to this `helm upgrade` command.**
+
+ ```bash
+ helm upgrade -n ambassador \
+ --set emissary-ingress.agent.enabled=true \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE=true \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ```
+
+8. **Finally, enable ACME in $productName$ $versionTwoX$.**
+
+ First, scale the 1.14 Ambassador to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador --replicas=0
+ ```
+
+ Once that's done, enable ACME in $productName$ $versionTwoX$. **Note that if you
+ needed to set `AMBASSADOR_LABEL_SELECTOR`, you must add that to this
+ `helm upgrade` command.**
+
+ ```bash
+ helm upgrade -n ambassador \
+ --set emissary-ingress.agent.enabled=true \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE= \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ````
+
+Congratulations! At this point, $productName$ $versionTwoX$ is fully running, and
+it's safe to remove the old `ambassador` and `ambassador-agent` Deployments:
+
+```
+kubectl delete -n ambassador deployment/ambassador deployment/ambassador-agent
+```
+
+Once $productName$ 1.14 is no longer running, you may [convert](../../../../convert-to-v3alpha1)
+any remaining `getambassador.io/v2` resources to `getambassador.io/v3alpha1`.
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.0/edge-stack-2.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.0/edge-stack-2.X.md
new file mode 100644
index 000000000..c9d337839
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.0/edge-stack-2.X.md
@@ -0,0 +1,92 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.0.5 to $productName$ $versionTwoX$ (Helm)
+
+
+ This guide covers migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation originally made using Helm.
+ If you did not install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Upgrading from $productName$ 2.0.5 to $productName$ $versionTwoX$ typically requires downtime.
+ In some situations, Ambassador Labs Support may be able to assist with a zero-downtime migration;
+ contact support with questions.
+
+
+Migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$ is a four-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Delete $productName$ 2.0.5 Deployment.**
+
+
+ Delete only the Deployment for $productName$ 2.0.5 in order to preserve all of
+ your existing configuration.
+
+
+ Use `kubectl` to delete the Deployment for $productName$ 2.0.5. Typically, this will be found
+ in the `ambassador` namespace.
+
+ ```
+ kubectl delete -n ambassador deployment edge-stack
+ ```
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $versionTwoX$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, install $productName$ in the `$productNamespace$` namespace. If necessary for
+ your installation (e.g. if you were running with `AMBASSADOR_SINGLE_NAMESPACE` set),
+ you can choose a different namespace.
+
+ ```bash
+ helm install -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 2.X.
+ Do not use the ambassador Helm chart.
+
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.4/edge-stack-2.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.4/edge-stack-2.X.md
new file mode 100644
index 000000000..f11054800
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.4/edge-stack-2.X.md
@@ -0,0 +1,75 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.4.X to $productName$ $versionTwoX$ (Helm)
+
+
+ This guide covers migrating from $productName$ 2.4 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between minor
+versions is straightforward.
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $versionTwoX$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 2.X.
+ Do not use the ambassador Helm chart.
+
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md
new file mode 100644
index 000000000..e0d02c0ec
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md
@@ -0,0 +1,154 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.5.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 2.5.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md
new file mode 100644
index 000000000..c988b1123
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md
@@ -0,0 +1,152 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.4.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 3.4.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x to 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md
new file mode 100644
index 000000000..ab9bbf890
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md
@@ -0,0 +1,152 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.7.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 3.7.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x to 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md
new file mode 100644
index 000000000..141473272
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md
@@ -0,0 +1,152 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.8.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 3.8.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x to 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/helm/emissary-3.8/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md
similarity index 99%
rename from docs/edge-stack/pre-release/topics/install/upgrade/helm/emissary-3.8/edge-stack-3.X.md
rename to docs/edge-stack/3.9/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md
index 488d4c0e5..e6dbff12b 100644
--- a/docs/edge-stack/pre-release/topics/install/upgrade/helm/emissary-3.8/edge-stack-3.X.md
+++ b/docs/edge-stack/3.9/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md
@@ -10,7 +10,7 @@ import Alert from '@material-ui/lab/Alert';
This guide is written for upgrading an installation originally made using Helm.
- If you did not install with Helm, see the YAML-based
+ If you did not install with Helm, see the YAML-based
upgrade instructions.
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-1.14/edge-stack-2.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-1.14/edge-stack-2.X.md
new file mode 100644
index 000000000..3dee2c343
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-1.14/edge-stack-2.X.md
@@ -0,0 +1,354 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 1.14.X to $productName$ $versionTwoX$ (YAML)
+
+
+ This guide covers migrating from $productName$ 1.14.X to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+We're pleased to introduce $productName$ $versionTwoX$! The 2.X family introduces a number of
+changes to allow $productName$ to more gracefully handle larger installations (including
+multitenant or multiorganizational installations), reduce memory footprint, and improve
+performance. In keeping with [SemVer](https://semver.org), $productName$ 2.X introduces
+some changes that aren't backward-compatible with 1.X. These changes are detailed in
+[Major Changes in $productName$ 2.X](../../../../../../about/changes-2.x).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $productName$ 1.14 and $productName$
+$versionTwoX$ side-by-side in the same cluster. This gives $productName$ $versionTwoX$
+and $productName$ 1.14 access to all the same configuration resources, with some
+important caveats:
+
+1. **$productName$ 1.14 will not see any `getambassador.io/v3alpha1` resources.**
+
+ This is intentional; it provides a way to apply configuration only to
+ $productName$ $versionTwoX$, while not interfering with the operation of your
+ $productName$ 1.14 installation.
+
+2. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $productName$ $versionTwoX$ installation from
+ seeing a particular bit of $productName$ 1.14 configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $productName$ $versionTwoX$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $productName$ $versionTwoX$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+3. **$productName$ 1.14 must remain in control of ACME while both installations are running.**
+
+ The processes that handle ACME challenges cannot be managed by both $productName$
+ 1.X and $productName$ $versionTwoX$ at the same time. The instructions below disable ACME
+ in $productName$ $versionTwoX$, allowing $productName$ 1.14 to continue managing it.
+
+ This implies that any new `Host`s used for $productName$ 1.14 should be created using
+ `getambassador.io/v2` so that $productName$ 1.14 can see them.
+
+4. **Check `AuthService` and `RateLimitService` resources, if any.**
+
+ If you have an [`AuthService`](../../../../../using/authservice/) or
+ [`RateLimitService`](../../../../../running/services/rate-limit-service) installed, make
+ sure that they are using the [namespace-qualified DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services).
+ If they are not, the initial migration tests may fail.
+
+ Additionally, when installing with Helm, you must make sure that $productName$ $versionTwoX$
+ does not attempt to create duplicate `AuthService` and `RateLimitService` entries. Add
+
+ ```
+ --set rateLimit.create=false
+ ```
+
+ and
+
+ ```
+ --set authService.create=false
+ ```
+
+ on the Helm command line to prevent duplicating these resources.
+
+5. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The migration YAML used below to install $productName$ $versionTwoX$ will not
+ install a duplicate agent. If you are building your own YAML, make sure not
+ to include a duplicate agent.
+
+6. **If you use ACME for multiple `Host`s, add a wildcard `Host` too.**
+
+ This is required to manage a known issue. This issue will be resolved in a future
+ $AESproductName$ release.
+
+7. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $productName$ 1.14 that use selectors that will match
+ Pods from $productName$ $versionTwoX$, traffic will be erroneously split between
+ $productName$ 1.14 and $productName$ $versionTwoX$. The labels used by $productName$
+ $versionTwoX$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $productName$ $versionTwoX$ in a separate cluster](../../../../migrate-to-2-alternate).
+This permits absolute certainty that your $productName$ 1.14 configuration will not be
+affected by changes meant for $productName$ $versionTwoX$, and it eliminates concerns about
+ACME, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is an eight-step process:
+
+1. **Make sure that older configuration resources are not present.**
+
+ $productName$ 2.X does not support `getambassador.io/v0` or `getambassador.io/v1`
+ resources, and Kubernetes will not permit removing support for CRD versions that are
+ still in use for stored resources. To verify that no resources older than
+ `getambassador.io/v2` are active, run
+
+ ```
+ kubectl get crds -o 'go-template={{range .items}}{{.metadata.name}}={{.status.storedVersions}}{{"\n"}}{{end}}' | fgrep getambassador.io
+ ```
+
+ If `v1` is present in the output, **do not begin migration.** The old resources must be
+ converted to `getambassador.io/v2` and the `storedVersion` information in the cluster
+ must be updated. If necessary, contact Ambassador Labs on [Slack](http://a8r.io/slack)
+ for more information.
+
+2. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you must configure your
+ Kubernetes cluster to support its new `getambassador.io/v3alpha1` configuration
+ resources. Note that `getambassador.io/v2` resources are still supported, but **you
+ must install support for `getambassador.io/v3alpha1`** to run $productName$ $versionTwoX$,
+ even if you intend to continue using only `getambassador.io/v2` resources for some
+ time.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, you need to install $productName$ $versionTwoX$ itself
+ **in the same namespace as your existing $productName$ 1.14 installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+ We publish three manifests for different namespaces. Use only the one that
+ matches the namespace into which you installed $productName$ 1.14:
+
+ - [`aes-emissaryns-migration.yaml`] for the `emissary` namespace;
+ - [`aes-defaultns-migration.yaml`] for the `default` namespace; and
+ - [`aes-ambassadorns-migration.yaml`] for the `ambassador` namespace.
+
+ All three files are set up as follows:
+
+ - They set the `AES_ACME_LEADER_DISABLE` environment variable to prevent $productName$ $versionTwoX$
+ from trying to manage ACME (leaving $productName$ 1.14 to do it instead).
+ - They do NOT set `AMBASSADOR_LABEL_SELECTOR`.
+ - They do NOT install the Ambassador Agent.
+ - They do NOT create an `AuthService` or a `RateLimitService`. It is very important that $productName$
+ $versionTwoX$ not attempt to create these resources, as they are already provided for your $productName$
+ 1.14 installation.
+
+ If any of these do not match your situation, download [`aes-ambassadorns-migration.yaml`] and edit it
+ as needed.
+
+ [`aes-emissaryns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-emissaryns-migration.yaml
+ [`aes-defaultns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-defaultns-migration.yaml
+ [`aes-ambassadorns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-ambassadorns-migration.yaml
+
+ Assuming you're using the `ambassador` namespace, as was typical for $productName$ 1.14:
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-ambassadorns-migration.yaml && \
+ kubectl rollout status -n ambassador deployment/aes -w
+ ```
+
+
+ Make sure that at most one installation of $productName$ is running without setting
+ the AES_ACME_LEADER_DISABLE flag. This prevents collisions in trying to manage
+ ACME.
+
+
+4. **Install `Listener`s and `Host`s as needed.**
+
+ An important difference between $productName$ 1.14 and $productName$ $versionTwoX$ is the
+ new **mandatory** `Listener` CRD. Also, when running both installations side by side,
+ you will need to make sure that a `Host` is present for the new $productName$ $versionTwoX$
+ Service. For example:
+
+ ```bash
+ kubectl apply -f - <
+ Make sure that any Hosts you create use API version getambassador.io/v2,
+ so that they can be managed by $productName$ 1.14 as long as both installations are running.
+
+
+ This example requires that you know the hostname for the $productName$ Service (`$EMISSARY_HOSTNAME`)
+ and that you have created a TLS Secret for it in `$EMISSARY_TLS_SECRET`.
+
+5. **Test!**
+
+ Your $productName$ $versionTwoX$ installation can support the `getambassador.io/v2`
+ configuration resources used by $productName$ 1.14, but you may need to make some
+ changes to the configuration, as detailed in the documentation on
+ [configuring $productName$ Communications](../../../../../../howtos/configure-communications)
+ and [updating CRDs to `getambassador.io/v3alpha1`](../../../../convert-to-v3alpha1).
+
+
+ Kubernetes will not allow you to have a getambassador.io/v3alpha1 resource
+ with the same name as a getambassador.io/v2 resource or vice versa: only
+ one version can be stored at a time.
+
+ If you find that your $productName$ $versionTwoX$ installation and your $productName$ 1.14
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 2, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your 1.14 CRDs, delete your
+ installation of $productName$ $versionTwoX$, and delete the `emissary-system` namespace.
+
+6. **When ready, switch over to $productName$ $versionTwoX$.**
+
+ You can run $productName$ 1.14 and $productName$ $versionTwoX$ side-by-side as long as you care
+ to. However, taking full advantage of $productName$ 2.X's capabilities **requires**
+ [updating your configuration to use `getambassador.io/v3alpha1` configuration resources](../../../../convert-to-v3alpha1),
+ since some useful features in $productName$ $versionTwoX$ are only available using
+ `getambassador.io/v3alpha1` resources.
+
+ When you're ready to have $productName$ $versionTwoX$ handle traffic on its own, switch
+ your original $productName$ 1.14 Service to point to $productName$ $versionTwoX$. Use
+ `kubectl edit -n ambassador service ambassador` and change the `selectors` to:
+
+ ```
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit -n ambassador service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+7. **Install the $productName$ $versionTwoX$ Ambassador Agent.**
+
+ First, scale the 1.14 agent to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent:
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-ambassadorns-agent.yaml && \
+ kubectl rollout status -n ambassador deployment/edge-stack-agent -w
+ ```
+
+8. **Finally, enable ACME in $productName$ $versionTwoX$.**
+
+ First, scale the 1.14 Ambassador to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador --replicase=0
+ ```
+
+ Once that's done, enable ACME in $productName$ $versionTwoX$:
+
+ ```bash
+ kubectl set env -n ambassador deployment/aes AES_ACME_LEADER_DISABLE-
+ kubectl rollout status -n ambassador deployment/aes -w
+ ````
+
+Congratulations! At this point, $productName$ $versionTwoX$ is fully running, and
+it's safe to remove the `ambassador` and `ambassador-agent` Deployments:
+
+```
+kubectl delete -n ambassador deployment/ambassador deployment/ambassador-agent
+```
+
+Once $productName$ 1.14 is no longer running, you may [convert](../../../../convert-to-v3alpha1)
+any remaining `getambassador.io/v2` resources to `getambassador.io/v3alpha1`.
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.0/edge-stack-2.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.0/edge-stack-2.X.md
new file mode 100644
index 000000000..23723ab84
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.0/edge-stack-2.X.md
@@ -0,0 +1,78 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.0.5 to $productName$ $versionTwoX$ (YAML)
+
+
+ This guide covers migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+
+ Upgrading from $productName$ 2.0.5 to $productName$ $versionTwoX$ typically requires downtime.
+ In some situations, Ambassador Labs Support may be able to assist with a zero-downtime migration;
+ contact support with questions.
+
+
+Migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$ is a three-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Delete $productName$ 2.0.5 Deployment.**
+
+
+ Delete only the Deployment for $productName$ 2.0.5 in order to preserve all of
+ your existing configuration.
+
+
+ Use `kubectl` to delete the Deployment for $productName$ 2.0.5. Typically, this will be found
+ in the `ambassador` namespace.
+
+ ```
+ kubectl delete -n ambassador deployment edge-stack
+ ```
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $versionTwoX$. This will install
+ in the `$productNamespace$` namespace. If necessary for your installation (e.g. if you were
+ running with `AMBASSADOR_SINGLE_NAMESPACE` set), you can download `aes.yaml` and edit as
+ needed.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.4/edge-stack-2.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.4/edge-stack-2.X.md
new file mode 100644
index 000000000..d8bde7af6
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.4/edge-stack-2.X.md
@@ -0,0 +1,60 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.4.X to $productName$ $versionTwoX$ (YAML)
+
+
+ This guide covers migrating from $productName$ 2.4 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between minor
+versions is straightforward.
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, upgrade $productName$ $versionTwoX$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md
new file mode 100644
index 000000000..948704ed1
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md
@@ -0,0 +1,127 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.5.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 2.5.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.24.1. Envoy has removed support for the Envoy V2 Transport Protocol and it is no longer valid. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally, support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes. Here are a few items that need to be checked or addressed before upgrading:
+
+1. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+2. Check removed runtime flags for behavior changes that may affect you:
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+3. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md
new file mode 100644
index 000000000..d342f175b
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md
@@ -0,0 +1,71 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.4.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 3.4.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+### Resources to check before migrating to $version$.
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read below before upgrading.
+
+
+$productName$ 3.4 has been upgraded from Envoy 1.23 to Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.7/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.7/edge-stack-3.X.md
new file mode 100644
index 000000000..435cc9dcc
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.7/edge-stack-3.X.md
@@ -0,0 +1,63 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.7.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 3.7.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md
new file mode 100644
index 000000000..819df4573
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md
@@ -0,0 +1,63 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.8.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 3.8.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/emissary-3.8/edge-stack-3.X.md b/docs/edge-stack/3.9/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md
similarity index 99%
rename from docs/edge-stack/pre-release/topics/install/upgrade/yaml/emissary-3.8/edge-stack-3.X.md
rename to docs/edge-stack/3.9/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md
index 00c66d937..0995e73cc 100644
--- a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/emissary-3.8/edge-stack-3.X.md
+++ b/docs/edge-stack/3.9/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md
@@ -10,7 +10,7 @@ import Alert from '@material-ui/lab/Alert';
This guide is written for upgrading an installation made without using Helm.
- If you originally installed with Helm, see the Helm-based
+ If you originally installed with Helm, see the Helm-based
upgrade instructions.
diff --git a/docs/edge-stack/3.9/topics/install/yaml-install.md b/docs/edge-stack/3.9/topics/install/yaml-install.md
new file mode 100644
index 000000000..da4b2d916
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/install/yaml-install.md
@@ -0,0 +1,93 @@
+---
+ description: In this guide, we'll walk through the process of deploying $productName$ in Kubernetes for ingress routing.
+---
+
+import Alert from '@material-ui/lab/Alert';
+
+# Install manually
+
+
+
+ To migrate from $productName$ 1.X to $productName$ 2.X, see the
+ [$productName$ migration matrix](../migration-matrix/). This guide
+ **will not work** for that, due to changes to the configuration
+ resources used for $productName$ 2.X.
+
+
+
+In this guide, we'll walk you through installing $productName$ in your Kubernetes cluster.
+
+The manual install process does not allow for as much control over configuration
+as the [Helm install method](../helm), so if you need more control over your $productName$
+installation, it is recommended that you use helm.
+
+## Before you begin
+
+
+ $productName$ requires a valid license or cloud connect token to start. You can refer to the quickstart guide
+ for instructions on how to obtain a free community license. Copy the cloud token command from the guide in Ambassador cloud for use below. If you already have a cloud connect token or
+ a valid enterprise license, then you can skip this step.
+
+
+$productName$ is designed to run in Kubernetes for production. The most essential requirements are:
+
+* Kubernetes 1.11 or later
+* The `kubectl` command-line tool
+
+## Install with YAML
+
+$productName$ is typically deployed to Kubernetes from the command line. If you don't have Kubernetes, you should use our [Docker](../docker) image to deploy $productName$ locally.
+
+1. In your terminal, run the following command:
+
+ ```
+ kubectl create namespace $productNamespace$ || true
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml && \
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl -n $productNamespace$ wait --for condition=available --timeout=90s deploy $productDeploymentName$
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the $productNamespace$ namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. Determine the IP address or hostname of your cluster by running the following command:
+
+ ```
+ kubectl get -n $productNamespace$ service $productDeploymentName$ -o "go-template={{range .status.loadBalancer.ingress}}{{or .ip .hostname}}{{end}}"
+ ```
+
+ Your load balancer may take several minutes to provision your IP address. Repeat the provided command until you get an IP address.
+
+3. Next Steps
+
+ $productName$ shold now be successfully installed and running, but in order to get started deploying Services and test routing to them you need to configure a few more resources.
+
+ - [The `Listener` Resource](../../running/listener/) is required to configure which ports the $productName$ pods listen on so that they can begin responding to requests.
+ - [The `Mapping` Resouce](../../using/intro-mappings/) is used to configure routing requests to services in your cluster.
+ - [The `Host` Resource](../../running/host-crd/) configures TLS termination for enablin HTTPS communication.
+ - Explore how $productName$ [configures communication with clients](../../../howtos/configure-communications)
+
+
+ We strongly recommend following along with our Quickstart Guide to get started by creating a Listener, deploying a simple service to test with, and setting up a Mapping to route requests from $productName$ to the demo service.
+
+
+## Upgrading an existing installation
+
+See the [migration matrix](../migration-matrix) for instructions about upgrading
+$productName$.
diff --git a/docs/edge-stack/3.9/topics/running/aes-extensions/authentication.md b/docs/edge-stack/3.9/topics/running/aes-extensions/authentication.md
new file mode 100644
index 000000000..79b005a66
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/aes-extensions/authentication.md
@@ -0,0 +1,78 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Authentication extension
+
+Edge Stack ships with an authentication service that is enabled
+to perform OAuth, JWT validation, and custom authentication schemes. It can
+perform different authentication schemes on different requests allowing you to
+enforce authentication as your application needs.
+
+The Filter and FilterPolicy resources are used to [configure how to do authentication](../../../using/filters). This doc focuses on how to deploy and manage the authentication extension.
+
+## Edge Stack configuration
+
+Edge Stack uses the [AuthService plugin](../../services/auth-service)
+to connect to the authentication extension.
+
+The default AuthService is named `ambassador-edge-stack-auth` and is defined
+as:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: AuthService
+metadata:
+ name: ambassador-edge-stack-auth
+ namespace: ambassador
+spec:
+ auth_service: 127.0.0.1:8500
+ proto: grpc
+ status_on_error:
+ code: 503
+ allow_request_body: false
+```
+
+This configures Envoy to talk to the extension process running on port 8500
+using gRPC and trim the body from the request when doing so. The default error
+code of 503 is usually overwritten by the Filter that is authenticating the
+request.
+
+This default AuthService works for most use cases. If you need to
+tune how Edge Stack connects to the authentication extension (like changing the
+default timeout), you can find the full configuration options in the
+[AuthService plugin docs](../../services/auth-service).
+
+## Authentication extension configuration
+
+Certain use cases may require some tuning of the authentication extension.
+Configuration of this extension is managed via environment variables.
+[The Ambassador container](../../environment) has a full list of environment
+variables available for configuration, including the variables used by the
+authentication extension.
+
+#### Redis
+
+The authentication extension uses Redis for caching the response from the
+`token endpoint` when performing OAuth.
+
+Edge Stack shares the same Redis pool for all features that use Redis. More information is available for [tuning Redis](../../aes-redis) if needed.
+
+#### Timeout variables
+
+The `AES_AUTH_TIMEOUT` environment variable configures the default timeout in
+the authentication extension.
+
+This timeout is necessary so that any error responses configured by Filters
+that the extension runs make their way to the client. Otherwise they would be
+overruled by the timeout from Envoy if a request takes longer than five seconds.
+
+If you have a long chain of Filters or a Filter that takes five or more seconds to respond,
+you can increase the timeout value to give your Filters enough time to run.
+
+
+The timeout_ms of the ambassador-edge-stack-auth AuthService defaults
+to a value of 5000 (five seconds). You will need to adjust this as well.
+
+AES_AUTH_TIMEOUT should always be around one second shorter than the timeout_ms of the AuthService to ensure Filter error responses make it to the client.
+
+The External Filter also have a timeout_ms field that must be set if a single Filter will take longer than five seconds.
+
diff --git a/docs/edge-stack/3.9/topics/running/aes-extensions/index.md b/docs/edge-stack/3.9/topics/running/aes-extensions/index.md
new file mode 100644
index 000000000..df71fcad6
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/aes-extensions/index.md
@@ -0,0 +1,33 @@
+# Ambassador Edge Stack extensions
+
+The Ambassador Edge Stack contains a number of pre-built extensions that make
+running, deploying, and exposing your applications in Kubernetes easier.
+
+Use of AES extensions is implemented via Kubernetes Custom Resources.
+Documentation for how to uses the various extensions can be found throughout the
+[Using AES AES for Developer](../../using/) section of the docs. This section
+is concerned with how to operate and tune deployment of these extensions in AES.
+
+## Redis
+
+Since AES does not use a database, Redis is uses for caching state information
+when an extension requires it.
+
+The Ambassador Edge Stack shares the same Redis pool for all features that use
+Redis.
+
+The [Redis documentation](../aes-redis) contains detailed information on tuning
+how AES talks to Redis.
+
+## The Extension process
+
+The various extensions of the Ambassador Edge Stack run as a separate process
+from the Ambassador control plane and Envoy data plane.
+
+### `AES_LOG_LEVEL`
+
+The `AES_LOG_LEVEL` controls the logging of all of the extensions in AES.
+
+Log level names are case-insensitive. From least verbose to most
+verbose, valid log levels are `error`, `warn`/`warning`, `info`,
+`debug`, and `trace`.
diff --git a/docs/edge-stack/3.9/topics/running/aes-extensions/ratelimit.md b/docs/edge-stack/3.9/topics/running/aes-extensions/ratelimit.md
new file mode 100644
index 000000000..51d2c6210
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/aes-extensions/ratelimit.md
@@ -0,0 +1,92 @@
+# Rate limiting extension
+
+The Ambassador Edge Stack ships with a rate limiting service that is enabled
+to perform advanced rate limiting out of the box.
+
+Configuration of the `Mapping` and `RateLimit` resources that control **how**
+to rate limit requests can be found in the
+[Rate Limiting](../../../using/rate-limits) section of the documentation.
+
+This document focuses on how to deploy and manage the rate limiting extension.
+
+## Ambassador configuration
+
+Ambassador uses the [`RateLimitService` plugin](../../services/rate-limit-service)
+to connect to the rate limiting extension in the Ambassador Edge Stack.
+
+The default `RateLimitService` is named `ambassador-edge-stack-ratelimit` and is
+defined as:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimitService
+metadata:
+ name: ambassador-edge-stack-ratelimit
+ namespace: ambassador
+spec:
+ service: 127.0.0.1:8500
+ failure_mode_deny: false # when set to true envoy will return 500 error when unable to communicate with RateLimitService
+ grpc:
+ use_resource_exhausted_code: true # default is false
+```
+
+- `failure_mode_deny` By default, $productName$ will fail open when unable to communicate with the service due to it becoming unvailable or due to timeouts. When this happens the upstream service that is being protected by a rate limit may be overloaded due to this behavior. When set to `true` $productName$ will be configured to return a `500` status code when it is unable to communicate with the RateLimit service and will fail closed by rejecting request to the upstream service.
+- `grpc` contains settings for grpc connections
+ - `use_resource_exhausted_code` By default, $productName$ will return an `UNAVAILABLE` gRPC code when a request is rate limited.
+ When set to `true`, this field will cause $productName$ will return a `RESOURCE_EXHAUSTED` gRPC code instead.
+
+This configures Envoy to send requests that are labeled for rate limiting to the
+extension process running on port 8500. The rate limiting extension will then
+use that request to count against any `RateLimit` whose pattern matches the
+request labels.
+
+## Authentication extension configuration
+
+Certain use cases may require some tuning of the rate limiting extension.
+Configuration of this extension is managed via environment variables.
+[The Ambassador Container](../../environment) has a full list of environment
+variables available for configuration. This document highlights the ones used
+by the rate limiting extension.
+
+### Redis
+
+The rate limiting extension relies heavily on redis for writing and reading
+counters for the different `RateLimit` patterns.
+
+The Ambassador Edge Stack shares the same Redis pool for all features that use
+Redis.
+
+See the [Redis documentation](../../aes-redis) for information on Redis tuning.
+
+#### REDIS_PERSECOND
+
+If `REDIS_PERSECOND` is true, a second Redis connection pool is created (to a
+potentially different Redis instance) that is only used for per-second
+RateLimits; this second connection pool is configured by the `REDIS_PERSECOND_*`
+variables rather than the usual `REDIS_*` variables.
+
+#### `AES_RATELIMIT_PREVIEW`
+
+Set `AES_RATELIMIT_PREVIEW` to `true` to access support for redis clustering,
+local caching, and an upgraded redis client with improved scalability in
+preview mode.
+
+#### `LOCAL_CACHE_SIZE_IN_BYTES`
+
+**Only available if `AES_RATELIMIT_PREVIEW: "true`.**
+
+The AES rate limit extension can optionally cache over-the-limit keys so it does
+not need to read the redis cache again for requests with labels that are already
+over the limit.
+
+Setting `LOCAL_CACHE_SIZE_IN_BYTES` to a non-zero value with enable local
+caching.
+
+#### `NEAR_LIMIT_RATIO`
+
+**Only available if `AES_RATELIMIT_PREVIEW: "true"`**
+
+Adjusts the ratio used by the `near_limit` statistic for tracking requests that
+are "near the limit".
+
+Defaults to `0.8` (80%) of the limit defined in the `RateLimit` rule.
diff --git a/docs/edge-stack/3.9/topics/running/aes-redis.md b/docs/edge-stack/3.9/topics/running/aes-redis.md
new file mode 100644
index 000000000..fe72b03be
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/aes-redis.md
@@ -0,0 +1,247 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Edge Stack and Redis
+
+The Ambassador Edge Stack make use of Redis for several purposes. By default,
+all components of the Ambassador Edge Stack share a Redis connection pool.
+
+## Rate Limit Service
+
+The rate limiting service, can be configured to use different connection pools
+for handling per-second rate limits or connecting to Redis clusters.
+
+### AES_RATELIMIT_PREVIEW
+
+Set `AES_RATELIMIT_PREVIEW` to `true` to access support for redis clustering,
+local caching, and an upgraded redis client with improved scalability in
+preview mode.
+
+### REDIS_PERSECOND
+
+If `REDIS_PERSECOND` is true, a second Redis connection pool is created (to a
+potentially different Redis instance) that is only used for per-second
+RateLimits; this second connection pool is configured by the `REDIS_PERSECOND_*`
+variables rather than the usual `REDIS_*` variables.
+
+## Redis layer 4 connectivity (L4)
+
+#### `SOCKET_TYPE`
+
+The Go network to use to talk to Redis. see [Go net.Dial](https://golang.org/src/net/dial.go)
+
+Most users will leave this as the default of `tcp`.
+
+#### `URL`
+
+The URL to dial to talk to Redis
+
+This will be either a hostname:port pair or a comma separated list of
+hostname:port pairs depending on the [TYPE](#redis-type) you are using.
+
+For `REDIS_URL` (but not `REDIS_PERSECOND_URL`), not setting a value disables
+Ambassador Edge Stack features that require Redis.
+
+#### `TLS_ENABLED`
+
+Specifies whether to use TLS when talking to Redis.
+
+#### `TLS_INSECURE`
+
+Specifies whether to skip certificate verification when
+using TLS to talk to Redis.
+
+Consider [installing the self-signed certificate for your Redis in to the
+Ambassador Edge Stack container](../../using/filters/#filters-using-self-signed-certificates)
+in order to leave certificate verification on.
+
+## Redis authentication (auth)
+
+**Default**
+
+Configure authentication to a redis pool using the default implementation.
+
+#### `PASSWORD`
+
+If set, it is used to [AUTH](https://redis.io/commands/auth) to Redis immediately after the connection is
+established.
+
+#### `USERNAME`
+
+If set, then that username is used with the password to log in as that user in
+the [Redis 6 ACL](https://redis.io/docs/manual/security/acl/). It is invalid to set a username without setting a
+password. It is invalid to set a username with Redis 5 or lower.
+
+The following YAML snippet is an example of configuring Redis authentication in the Ambassador deployment's environment variables.
+
+```yaml
+env:
+- name: REDIS_USERNAME:
+ value: "default"
+- name: REDIS_PASSWORD:
+ valueFrom:
+ secretKeyRef:
+ key: password
+ name: ambassador-redis-password
+```
+
+
+ This example demonstrates getting the redis password from a secret called ambassador-redis-password instead
+ of providing the value directly.
+
+
+**Rate Limit Preview**
+
+Configure authentication to a redis pool using the preview rate limiting
+implementation
+
+#### `AUTH`
+
+Required for authentication with Rate Limit Preview. You must also configure `REDIS_USERNAME`
+and `REDIS_PASSWORD` for the rest of Ambassador's Redis usage.
+
+If you configure `REDIS_AUTH`, then `REDIS_USERNAME` cannot be changed from the value `default`, and
+`REDIS_PASSWORD` should contain the same value as `REDIS_AUTH`.
+
+`REDIS_USERNAME` and `REDIS_PASSWORD` handle all Redis authentication that is separate from Rate Limit Preview so
+failing to set them when using `REDIS_AUTH` will result in Ambassador not being able to authenticate with Redis for
+all of its other functionality.
+
+Adding `AUTH` to the example above for rate limit preview would look like the following snippet.
+
+```yaml
+env:
+- name: REDIS_USERNAME:
+ value: "default"
+- name: REDIS_PASSWORD:
+ valueFrom:
+ secretKeyRef:
+ key: password
+ name: ambassador-redis-password
+- name: REDIS_AUTH
+ valueFrom:
+ secretKeyRef:
+ key: password
+ name: ambassador-redis-password
+```
+
+
+ Setting AUTH without USERNAME and PASSWORD can result in various problems since AUTH does not
+ overwrite the basic Redis authentication behavior for systems outside of rate limit preview.
+
+
+## Redis performance tuning (tune)
+
+#### `POOL_SIZE`
+
+The number of connections to keep around when idle.
+
+The total number of connections may go lower than this if there are errors.
+
+The total number of connections may go higher than this during a load surge.
+
+#### `PING_INTERVAL`
+
+The rate at which Ambassador will ping the idle connections in the normal pool
+(not extra connections created for a load surge).
+
+Ambassador will `PING` one of them every `PING_INTERVAL÷POOL_SIZE` so
+that each connection will on average be `PING`ed every `PING_INTERVAL`.
+
+#### `TIMEOUT`
+
+Sets 4 different timeouts:
+
+1. `(*net.Dialer).Timeout` for establishing connections
+2. `(*redis.Client).ReadTimeout` for reading a single complete response
+3. `(*redis.Client).WriteTimeout` for writing a single complete request
+4. The timeout when waiting for a connection to become available from the
+ pool (not including the dial time, which is timed out separately)
+
+A value of "0" means "no timeout".
+
+#### `SURGE_LIMIT_INTERVAL`
+
+During a load surge, if the pool is depleted, then Ambassador may create new
+connections to Redis in order to fulfill demand, at a maximum rate of one new
+connection per `SURGE_LIMIT_INTERVAL`.
+
+A value of "0" (the default) means "allow new connections to be created as
+fast as necessary.
+
+The total number of connections that Ambassador can surge to is unbounded.
+
+#### `SURGE_LIMIT_AFTER`
+
+The number of connections that can be created _after_ the normal pool is
+depleted before `SURGE_LIMIT_INTERVAL` kicks in.
+
+The first `POOL_SIZE+SURGE_LIMIT_AFTER` connections are allowed to
+be created as fast as necessary.
+
+This setting has no effect if `SURGE_LIMIT_INTERVAL` is 0.
+
+#### `SURGE_POOL_SIZE`
+
+Normally during a surge, excess connections beyond `POOL_SIZE` are
+closed immediately after they are done being used, instead of being returned
+to a pool.
+
+`SURGE_POOL_SIZE` configures a "reserve" pool for excess connections
+created during a surge.
+
+Excess connections beyond `POOL_SIZE+SURGE_POOL_SIZE` will still
+be closed immediately after use.
+
+#### `SURGE_POOL_DRAIN_INTERVAL`
+
+How quickly to drain connections from the surge pool after a surge is over.
+
+Connections are closed at a rate of one connection per
+`SURGE_POOL_DRAIN_INTERVAL`.
+
+This setting has no effect if `SURGE_POOL_SIZE` is 0.
+
+## Redis type
+
+Redis currently support three different deployment methods. Ambassador Edge
+Stack can now support using a Redis deployed in any of these ways for rate
+limiting when `AES_RATELIMIT_PREVIEW=true`.
+
+#### `TYPE`
+
+- `SINGLE`: Talk to a single instance of redis, or a redis proxy.
+
+ Requires the redis `REDIS_URL` or `REDIS_PERSECOND_URL` to be either a
+ single hostname:port pair or a unix domain socket reference.
+
+- `SENTINEL`: Talk to a redis deployment with sentinel instances (see
+ https://redis.io/topics/sentinel).
+
+ Requires the redis `REDIS_URL` or `REDIS_PERSECOND_URL` to be a comma
+ separated list with the first string as the master name of the sentinel
+ cluster followed by hostname:port pairs. The list size should be >= 2.
+ The first item is the name of the master and the rest are the sentinels.
+
+- `CLUSTER`: Talk to a redis in cluster mode (see
+ https://redis.io/topics/cluster-spec)
+
+ Requires the redis `REDIS_URL` or `REDIS_PERSECOND_URL` to be either a
+ single hostname:port pair of the read/write endpoint or a comma separated
+ list of hostname:port pairs with all the nodes in the cluster.
+
+ `PIPELINE_WINDOW` must be set when `TYPE: CLUSTER`.
+
+#### `PIPELINE_WINDOW`
+
+The duration after which internal pipelines will be flushed.
+
+If window is zero then implicit pipelining will be disabled.
+
+> `150us` is recommended when using implicit pipelining in production.
+
+#### `PIPELINE_LIMIT`
+
+The maximum number of commands that can be pipelined before flushing.
+
+If limit is zero then no limit will be used and pipelines will only be limited
+by the specified time window.
diff --git a/docs/edge-stack/3.9/topics/running/ambassador.md b/docs/edge-stack/3.9/topics/running/ambassador.md
new file mode 100644
index 000000000..fb8a5fa4e
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/ambassador.md
@@ -0,0 +1,619 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Ambassador** **Module** Resource
+
+
+
+If present, the `ambassador` `Module` defines system-wide configuration for $productName$. **You may very well not need this resource.** To use the `ambassador` `Module` to configure $productName$, it MUST be named `ambassador`, otherwise it will be ignored. To create multiple `ambassador` `Module`s in the same Kubernetes namespace, you will need to apply them as annotations with separate `ambassador_id`s: you will not be able to use multiple CRDs.
+
+The defaults in the `ambassador` `Module` are:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+# Use ambassador_id only if you are using multiple instances of $productName$ in the same cluster.
+# See below for more information.
+ ambassador_id: [ "" ]
+ config:
+ # Use the items below for config fields
+```
+
+There are many config field items that can be configured on the `ambassador` `Module`. They are listed below with examples and grouped by category.
+
+## Envoy
+
+##### Content-Length headers
+
+* `allow_chunked_length: true` tells Envoy to allow requests or responses with both `Content-Length` and `Transfer-Encoding` headers set.
+
+By default, messages with both `Content-Length` and `Content-Transfer-Encoding` are rejected. If `allow_chunked_length` is `true`, $productName$ will remove the `Content-Length` header and process the message. See the [Envoy documentation for more details](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto.html?highlight=allow_chunked_length#config-core-v3-http1protocoloptions).
+
+##### Envoy access logs
+
+* `envoy_log_path` defines the path of Envoy's access log. By default this is standard output.
+* `envoy_log_type` defines the type of access log Envoy will use. Currently, only `json` or `text` are supported.
+* `envoy_log_format` defines the Envoy access log line format.
+
+These logs can be formatted using [Envoy operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) to display specific information about an incoming request. The example below will show only the protocol and duration of a request:
+
+```yaml
+envoy_log_path: /dev/fd/1
+envoy_log_type: json
+envoy_log_format:
+ {
+ "protocol": "%PROTOCOL%",
+ "duration": "%DURATION%"
+ }
+```
+
+See the Envoy documentation for the [standard log format](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#default-format-string) and a [complete list of log format operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/access_log).
+
+##### Envoy validation timeout
+
+* `envoy_validation_timeout` defines the timeout, in seconds, for validating a new Envoy configuration.
+
+The default is 10; a value of 0 disables Envoy configuration validation. Most installations will not need to use this setting.
+
+For example:
+
+```yaml
+envoy_validation_timeout: 30
+```
+
+would allow 30 seconds to validate the generated Envoy configuration.
+
+##### Error response overrides
+
+* `error_response_overrides` permits changing the status code and body text for 4XX and 5XX response codes.
+
+By default, $productName$ will pass through error responses without modification, and errors generated locally will use Envoy's default response body, if any.
+
+See [using error response overrides](../custom-error-responses) for usage details. For example, this configuration:
+
+```yaml
+error_response_overrides:
+ - on_status_code: 404
+ body:
+ text_format: "File not found"
+```
+
+would explicitly modify the body of 404s to say "File not found".
+
+##### Forwarding client cert details
+
+Two attributes allow providing information about the client's TLS certificate to upstream certificates:
+
+* `forward_client_cert_details: true` will tell Envoy to add the `X-Forwarded-Client-Cert` to upstream
+ requests.
+* `set_current_client_cert_details` will tell Envoy what information to include in the
+ `X-Forwarded-Client-Cert` header.
+
+$productName$ will not forward information about a certificate that it cannot validate.
+
+See the Envoy documentation on [X-Forwarded-Client-Cert](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html?highlight=xfcc#x-forwarded-client-cert) and [SetCurrentClientCertDetails](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto.html#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-setcurrentclientcertdetails) for more information.
+
+```yaml
+forward_client_cert_details: true
+set_current_client_cert_details: SANITIZE
+```
+
+##### Server name
+
+* `server_name` allows overriding the server name that Envoy sends with responses to clients.
+
+By default, Envoy uses a server name of `envoy`.
+
+##### Suppress Envoy headers
+
+* `suppress_envoy_headers: true` will prevent $productName$ from emitting certain additional
+ headers to HTTP requests and responses.
+
+For the exact set of headers covered by this config, see the [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-headers-set)
+
+---
+## General
+
+##### Ambassador ID
+
+* `ambassador_id` allows using multiple instances of $productName$ in the same cluster.
+
+We recommend _not_ setting `ambassador_id` if you are running only one instance of $productName$ in your cluster. For more information, see the [Running and Deployment documentation](../running/#ambassador_id).
+
+If used, the `ambassador_id` value must be an array, for example:
+
+```yaml
+ambassador_id: [ "test_environment" ]
+```
+
+##### Defaults
+
+* `defaults` provides a dictionary of default values that will be applied to various $productName$ resources.
+
+See [Using `ambassador` `Module` Defaults](../../using/defaults) for more information.
+
+---
+
+## gRPC
+
+##### Bridges
+
+* `enable_grpc_http11_bridge: true` will enable the gRPC-HTTP/1.1 bridge.
+* `enable_grpc_web: true` will enable the gRPC-Web bridge.
+
+gRPC is a binary HTTP/2-based protocol. While this allows high performance, it can be problematic for clients that are unable to speak HTTP/2 (such as JavaScript in many browsers, or legacy clients in difficult-to-update environments).
+
+The gRPC-HTTP/1.1 bridge can translate HTTP/1.1 calls with `Content-Type: application/grpc` into gRPC calls: $productName$ will perform buffering and translation as necessary. For more details on the translation process, see the [Envoy gRPC HTTP/1.1 bridge documentation](https://www.envoyproxy.io/docs/envoy/v1.11.2/configuration/http_filters/grpc_http1_bridge_filter.html).
+
+Likewise, gRPC-Web is a JSON and HTTP-based protocol that allows browser-based clients to take advantage of gRPC protocols. The gRPC-Web specification requires a server-side proxy to translate between gRPC-Web requests and gRPC backend services, and $productName$ can fill this role when the gRPC-Web bridge is enabled. For more details on the translation process, see the [Envoy gRPC HTTP/1.1 bridge documentation](https://www.envoyproxy.io/docs/envoy/v1.11.2/configuration/http_filters/grpc_http1_bridge_filter.html); for more details on gRPC-Web itself, see the [gRPC-Web client GitHub repo](https://github.com/grpc/grpc-web).
+
+##### Statistics
+
+* `grpc_stats` allows enabling telemetry for gRPC calls using Envoy's [gRPC Statistics Filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_stats_filter).
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ grpc_stats:
+ upstream_stats: true
+ services:
+ - name: .
+ method_names: []
+```
+
+Supported parameters:
+* `all_methods`
+* `services`
+* `upstream_stats`
+
+Available metrics:
+* `envoy_cluster_grpc__`
+* `envoy_cluster_grpc__request_message_count`
+* `envoy_cluster_grpc__response_message_count`
+* `envoy_cluster_grpc__success`
+* `envoy_cluster_grpc__total`
+* `envoy_cluster_grpc_upstream_` - **only when `upstream_stats: true`**
+
+Please note that `` will only be present if `all_methods` is set or the service and the method are present under `services`. If `all_methods` is false or the method is not on the list, the available metrics will be in the format `envoy_cluster_grpc_`.
+
+* `all_methods`: If set to true, emit stats for all service/method names.
+If set to false, emit stats for all service/message types to the same stats without including the service/method in the name.
+**This option is only safe if all clients are trusted. If this option is enabled with untrusted clients, the clients could cause unbounded growth in the number
+of stats in Envoy, using unbounded memory and potentially slowing down stats pipelines.**
+
+* `services`: If set, specifies an allow list of service/methods that will have individual stats emitted for them. Any call that does not match the allow list will be counted in a stat with no method specifier (generic metric).
+
+
+ If both all_methods and services are present, all_methods will be ignored.
+
+
+* `upstream_stats`: If true, the filter will gather a histogram for the request time of the upstream.
+
+---
+
+## Header behavior
+
+##### Header case
+
+* `proper_case: true` forces headers to have their "proper" case as shown in RFC7230.
+* `header_case_overrides` allows forcing certain headers to have specific casing.
+
+proper_case and header_case_overrides are mutually exclusive.
+
+RFC7230 specifies that HTTP header names are case-insensitive, but always shows and refers to headers as starting with a capital letter, continuing in lowercase, then repeating the single capital letter after each non-alpha character. This has become an established convention when working with HTTP:
+
+- `Host`, not `host` or `HOST`
+- `Content-Type`, not `content-type`, `Content-type`, or `cOnTeNt-TyPe`
+
+Internally, Envoy typically uses [all lowercase](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/header_casing) for header names. This is fully compliant with RFC7230, but some services and clients may require headers to follow the stricter casing rules implied by RFC7230 section headers: in that situation, setting `proper_case: true` will tell Envoy to force all headers to use the casing above.
+
+Alternately, it is also possible - although less common - for services or clients to require some other specific casing for specific headers. `header_case_overrides` specifies an array of header names: if a case-insensitive match for a header is found in the list, the matching header will be replaced with the one in the list. For example, the following configuration will force headers that match `X-MY-Header` and `X-EXPERIMENTAL` to use that exact casing, regardless of the original case used in flight:
+
+```yaml
+header_case_overrides:
+- X-MY-Header
+- X-EXPERIMENTAL
+```
+
+If the upstream service responds with `x-my-header: 1`, $productName$ will return `X-MY-Header: 1` to the client. Similarly, if the client includes `x-ExperiMENTAL: yes` in its request, the request to the upstream service will include `X-EXPERIMENTAL: yes`. Other headers will not be altered; $productName$ will use its default lowercase header.
+
+Please see the [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto.html#config-core-v3-http1protocoloptions-headerkeyformat) for more information. Note that in general, we recommend updating clients and services rather than relying on `header_case_overrides`.
+
+##### Linkerd interoperability
+
+* `add_linkerd_headers: true` will force $productName$ to include the `l5d-dst-override` header for Linkerd.
+
+When using older Linkerd installations, requests going to an upstream service may need to include the `l5d-dst-override` header to ensure that Linkerd will route them correctly. Setting `add_linkerd_headers` does this automatically. See the [Mapping](../../using/mappings#linkerd-interoperability-add_linkerd_headers) documentation for more details.
+
+##### Max request headers size
+
+* `max_request_headers_kb` sets the maximum allowed request header size in kilobytes. If not set, the default is 60 KB.
+
+See [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto.html) for more information.
+
+##### Preserve external request ID
+
+* `preserve_external_request_id: true` will preserve any `X-Request-Id` header presented by the client. The default is `false`, in which case Envoy will always generate a new `X-Request-Id` value.
+
+##### Strip matching host port
+
+* `strip_matching_host_port: true` will tell $productName$ to strip any port number from the host/authority header before processing and routing the request if that port number matches the port number of Envoy's listener. The default is `false`, which will preserve any port number.
+
+In the default installation of $productName$ the public port is 443, which then maps internally to 8443, so this only works in custom installations where the public Service port and Envoy listener port match.
+
+A common reason to try using this property is if you are using gRPC with TLS and your client library appends the port to the Host header (i.e. `myurl.com:443`). We have an alternative solution in our [gRPC guide](../../../../../emissary/pre-release/howtos/grpc#working-with-host-headers-that-include-the-port) that uses a [Lua script](#lua-scripts) to remove all ports from every Host header for that use case.
+
+---
+
+## Miscellaneous
+
+
+##### Envoy's admin port
+
+* `admin_port` specifies the port where $productName$'s Envoy will listen for low-level admin requests. The default is 8001; it should almost never need changing.
+
+##### Lua scripts
+
+* `lua_scripts` allows defining a custom Lua script to run on every request.
+
+This is useful for simple use cases that mutate requests or responses, for example to add a custom header:
+
+```yaml
+lua_scripts: |
+ function envoy_on_response(response_handle)
+ response_handle:headers():add("Lua-Scripts-Enabled", "Processed")
+ end
+```
+
+For more details on the Lua API, see the [Envoy Lua filter documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter.html).
+
+Some caveats around the embedded scripts:
+
+* They run in-process, so any bugs in your Lua script can break every request.
+* They're run on every request/response to every URL.
+* They're inlined in the $productName$ YAML; as such, we do not recommend using Lua scripts for long, complex logic.
+
+If you need more flexible and configurable options, $AESproductName$ supports a [pluggable Filter system](../../using/filters/).
+
+##### Merge slashes
+
+* `merge_slashes: true` will cause $productName$ to merge adjacent slashes in incoming paths when doing route matching and request filtering: for example, a request for `//foo///bar` would be matched to a `Mapping` with prefix `/foo/bar`.
+
+##### Modify Default Buffer Size
+
+By default, the Envoy that ships with $productName$ uses a defailt of 1MiB soft limit for an upstream service's read and write buffer limits. This setting allows you to configure that buffer limit. See the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto.html?highlight=per_connection_buffer_limit_bytes) for more information.
+
+```yaml
+buffer_limit_bytes: 5242880 # Sets the default buffer limit to 5 MiB
+```
+
+##### Use $productName$ namespace for service resolution
+
+* `use_ambassador_namespace_for_service_resolution: true` tells $productName$ to assume that unqualified services are in the same namespace as $productName$
+
+By default, when $productName$ sees a service name without a namespace, it assumes that the namespace is the same as the resource referring to the service. For example, for this `Mapping`:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-1
+ namespace: foo
+spec:
+ hostname: "*"
+ prefix: /
+ service: upstream
+```
+
+$productName$ would look for a Service named `upstream` in namespace `foo`.
+
+However, if `use_ambassador_namespace_for_service_resolution` is `true`, this `Mapping` would look for a Service named `foo` in the namespace in which $productName$ is installed instead.
+
+---
+
+## Observability
+
+##### Diagnostics
+
+* `diagnostics` controls access to the diagnostics interface.
+
+By default, $productName$ creates a `Mapping` that allows access to the diagnostic interface at `/ambassador/v0/diag` from anywhere in the cluster. To disable the `Mapping` entirely, set `diagnostics.enabled` to `false`:
+
+
+```yaml
+diagnostics:
+ enabled: false
+```
+
+With diagnostics disabled, `/ambassador/v0/diag` will respond with 404; however, the service itself is still running, and `/ambassador/v0/diag/` is reachable from inside the $productName$ Pod at `https://localhost:8877`. You can use Kubernetes port forwarding to set up remote access to the diagnostics page temporarily:
+
+```
+kubectl port-forward -n ambassador deploy/ambassador 8877
+```
+
+Alternately, to leave the `Mapping` intact but restrict access to only the local Pod, set `diagnostics.allow_non_local` to `false`:
+
+```yaml
+diagnostics:
+ allow_non_local: true
+```
+
+See [Protecting Access to the Diagnostics Interface](../../../howtos/protecting-diag-access) for more information.
+
+---
+## Protocols
+
+##### Enable IPv4 and IPv6
+
+* `enable_ipv4` determines whether IPv4 DNS lookups are enabled. The default is `true`.
+* `enable_ipv6` determines whether IPv6 DNS lookups are enabled. The default is `false`.
+
+If both IPv4 and IPv6 are enabled, $productName$ will prefer IPv6. This can have strange effects if $productName$ receives `AAAA` records from a DNS lookup, but the underlying network of the pod doesn't actually support IPv6 traffic. For this reason, the default is IPv4 only.
+
+A [`Mapping`](../../using/mappings) can override both `enable_ipv4` and `enable_ipv6`, but if either is not stated explicitly in a `Mapping`, the values here are used. Most $productName$ installations will probably be able to avoid overriding these settings in Mappings.
+
+##### HTTP/1.0 support
+
+* `enable_http10: true` will enable handling incoming HTTP/1.0 and HTTP/0.9 requests. The default is `false`.
+
+---
+## Security
+
+##### Cross origin resource sharing (CORS)
+
+* `cors` sets the default CORS configuration for all mappings in the cluster. See the [CORS syntax](../../using/cors).
+
+For example:
+
+```yaml
+cors:
+ origins: http://foo.example,http://bar.example
+ methods: POST, GET, OPTIONS
+ ...
+```
+
+##### IP allow and deny
+
+* `ip_allow` and `ip_deny` define HTTP source IP address ranges to allow or deny.
+
+Only one of ip_allow and ip_deny may be specified.
+
+The default is to allow all traffic.
+
+If `ip_allow` is specified, any traffic not matching a range to allow will be denied. If `ip_deny` is specified, any traffic not matching a range to deny will be allowed. A list of ranges to allow and a separate list to deny may not both be specified.
+
+Both take a list of IP address ranges with a keyword specifying how to interpret the address, for example:
+
+```yaml
+ip_allow:
+- peer: 127.0.0.1
+- remote: 99.99.0.0/16
+```
+
+The keyword `peer` specifies that the match should happen using the IP address of the other end of the network connection carrying the request: `X-Forwarded-For` and the `PROXY` protocol are both ignored. Here, our example specifies that connections originating from the $productName$ pod itself should always be allowed.
+
+The keyword `remote` specifies that the match should happen using the IP address of the HTTP client, taking into account `X-Forwarded-For` and the `PROXY` protocol if they are allowed (if they are not allowed, or not present, the peer address will be used instead). This permits matches to behave correctly when, for example, $productName$ is behind a layer 7 load balancer. Here, our example specifies that HTTP clients from the IP address range `99.99.0.0` - `99.99.255.255` will be allowed.
+
+You may specify as many ranges for each kind of keyword as desired.
+
+##### Rejecting Client Requests With Escaped Slashes
+
+* `reject_requests_with_escaped_slashes: true` will tell $productName$ to reject requests containing escaped slashes.
+
+When set to `true`, $productName$ will reject any client requests that contain escaped slashes (`%2F`, `%2f`, `%5C`, or `%5c`) in their URI path by returning HTTP 400. By default, $productName$ will forward these requests unmodified.
+
+ - **Envoy and $productName$ behavior**
+
+ Internally, Envoy treats escaped and unescaped slashes distinctly for matching purposes. This means that an $productName$ mapping
+ for path `/httpbin/status` will not be matched by a request for `/httpbin%2fstatus`.
+
+ On the other hand, when using $productName$, escaped slashes will be treated like unescaped slashes when applying FilterPolicies. For example, a request to `/httpbin%2fstatus/200` will be matched against a FilterPolicy for `/httpbin/status/*`.
+
+ - **Security Concern Example**
+
+ With $productName$, this can become a security concern when combined with `bypass_auth` in the following scenario:
+
+ - Use a `Mapping` for path `/prefix` with `bypass_auth` set to true. The intention here is to apply no FilterPolicies under this prefix, by default.
+
+ - Use a `Mapping` for path `/prefix/secure/` without setting bypass_auth to true. The intention here is to selectively apply a FilterPolicy to this longer prefix.
+
+ - Have an upstream service that receives both `/prefix` and `/prefix/secure/` traffic (from the Mappings above), but the upstream service treats escaped and unescaped slashes equivalently.
+
+ In this scenario, when a client makes a request to `/prefix%2fsecure/secret.txt`, the underlying Envoy configuration will _not_ match the routing rule for `/prefix/secure/`, but will instead
+ match the routing rule for `/prefix` which has `bypass_auth` set to true. $productName$ FilterPolicies will _not_ be enforced in this case, and the upstream service will receive
+ a request that it treats equivalently to `/prefix/secure/secret.txt`, potentially leaking information that was assumed protected by an $productName$ FilterPolicy.
+
+ One way to avoid this particular scenario is to avoid using `bypass_auth` and instead use a FilterPolicy that applies no filters when no authorization behavior is desired.
+
+ The other way to avoid this scenario is to reject client requests with escaped slashes altogether to eliminate this class of path confusion security concerns. This is recommended when there is no known, legitimate reason to accept client requests that contain escaped slashes. This is especially true if it is not known whether upstream services will treat escaped and unescaped slashes equivalently.
+
+ This document is not intended to provide an exhaustive set of scenarios where path confusion can lead to security concerns. As part of good security practice it is recommended to audit end-to-end request flow and the behavior of each component’s escaped path handling to determine the best configuration for your use case.
+
+ - **Summary**
+
+ Envoy treats escaped and unescaped slashes _distinctly_ for matching purposes. Matching is the underlying behavior used by $productName$ Mappings.
+
+ $productName$ treats escaped and unescaped slashes _equivalently_ when selecting FilterPolicies. FilterPolicies are applied by $productName$ after Envoy has performed route matching.
+
+ Finally, whether upstream services treat escaped and unescaped slashes equivalently is entirely dependent on the upstream service, and therefore dependent on your use case. Configuration intended to implement security policies will require audit with respect to escaped slashes. By setting reject_requests_with_escaped_slashes, this class of security concern can largely be eliminated.
+
+##### Trust downstream client IP
+
+* `use_remote_address: false` tells $productName$ that it cannot trust the remote address of incoming connections, and must instead rely exclusively on the `X-Forwarded-For` header.
+
+When `true` (the default), $productName$ will append its own IP address to the `X-Forwarded-For` header so that upstream services of $productName$ can get the full set of IP addresses that have propagated a request. You may also need to set `externalTrafficPolicy: Local` on your `LoadBalancer` to propagate the original source IP address.
+
+See the [Envoy documentation on the `X-Forwarded-For header` ](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers) and the [Kubernetes documentation on preserving the client source IP](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip) for more details.
+
+##### `X-Forwarded-For` trusted hops
+
+* `xff_num_trusted_hops` sets how many L7 proxies ahead of $productName$ should be trusted.
+
+
+ This value is not dynamically configurable in Envoy. A restart is required changing the value of xff_num_trusted_hops for Envoy to respect the change.
+
+
+The value of `xff_num_trusted_hops` indicates the number of trusted proxies in front of $productName$. The default setting is 0 which tells Envoy to use the immediate downstream connection's IP address as the trusted client address. The trusted client address is used to populate the `remote_address` field used for rate limiting and can affect which IP address Envoy will set as `X-Envoy-External-Address`.
+
+`xff_num_trusted_hops` behavior is determined by the value of `use_remote_address` (which is true by default).
+
+* If `use_remote_address` is false and `xff_num_trusted_hops` is set to a value N that is greater than zero, the trusted client address is the (N+1)th address from the right end of XFF. (If the XFF contains fewer than N+1 addresses, Envoy falls back to using the immediate downstream connection’s source address as a trusted client address.)
+
+* If `use_remote_address` is true and `xff_num_trusted_hops` is set to a value N that is greater than zero, the trusted client address is the Nth address from the right end of XFF. (If the XFF contains fewer than N addresses, Envoy falls back to using the immediate downstream connection’s source address as a trusted client address.)
+
+Refer to [Envoy's documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html#x-forwarded-for) for some detailed examples of this interaction.
+
+---
+
+## Service health / timeouts
+
+##### Incoming connection idle timeout
+
+* `listener_idle_timeout_ms` sets the idle timeout for incoming connections.
+
+If set, this specifies the length of time (in milliseconds) that an incoming connection is allowed to be idle before being dropped. This can useful if you have proxies and/or firewalls in front of $productName$ and need to control how $productName$ initiates closing an idle TCP connection.
+
+If not set, the default is no timeout, meaning that incoming connections may remain idle forever.
+
+Please see the [Envoy documentation on HTTP protocol options](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#config-core-v3-httpprotocoloptions) for more information.
+
+##### Keepalive
+
+* `keepalive` sets the global TCP keepalive settings.
+
+$productName$ will use these settings for all `Mapping`s unless overridden in a `Mapping`'s configuration. Without `keepalive`, $productName$ follows the operating system defaults.
+
+For example, the following configuration:
+
+```yaml
+keepalive:
+ time: 2
+ interval: 2
+ probes: 100
+```
+
+would enable keepalives every two seconds (`interval`), starting after two seconds of idleness (`time`), with the connection being dropped if 100 keepalives are sent with no response (`probes`). For more information, see the [Envoy keepalive documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto.html#config-core-v3-tcpkeepalive).
+
+##### Upstream idle timeout
+
+* `cluster_idle_timeout_ms` sets the default idle timeout for upstream connections (by default, one hour).
+
+If set, this specifies the timeout (in milliseconds) after which an idle connection upstream is closed. The idle timeout can be completely disabled by setting `cluster_idle_timeout_ms: 0`, which risks idle upstream connections never getting closed.
+
+If not set, the default idle timeout is one hour.
+
+You can override this setting with [`idle_timeout_ms` on a `Mapping`](../../using/timeouts/).
+
+##### Upstream max lifetime
+
+* `cluster_max_connection_lifetime_ms` sets the default maximum lifetime of an upstream connection.
+
+If set, this specifies the maximum amount of time (in milliseconds) after which an upstream connection is drained and closed, regardless of whether it is idle or not. Connection recreation incurs additional overhead when processing requests. The overhead tends to be nominal for plaintext (HTTP) connections within the same cluster, but may be more significant for secure HTTPS connections or upstreams with high latency. For this reason, it is generally recommended to set this value to at least 10000 ms to minimize the amortized cost of connection recreation while providing a reasonable bound for connection lifetime.
+
+If not set (or set to zero), then upstream connections may remain open for arbitrarily long.
+
+You can override this setting with [`cluster_max_connection_lifetime_ms` on a `Mapping`](../../using/timeouts/).
+
+##### Request timeout
+
+* `cluster_request_timeout_ms` sets the default end-to-end timeout for a single request.
+
+If set, this specifies the default end-to-end timeout for every request.
+
+If not set, the default is three seconds.
+
+You can override this setting with [`timeout_ms` on a `Mapping`](../../using/timeouts/).
+
+##### Readiness and liveness probes
+
+* `readiness_probe` sets whether `/ambassador/v0/check_ready` is automatically mapped
+* `liveness_probe` sets whether `/ambassador/v0/check_alive` is automatically mapped
+
+By default, $productName$ creates `Mapping`s that support readiness and liveness checks at `/ambassador/v0/check_ready` and `/ambassador/v0/check_alive`. To disable the readiness `Mapping` entirely, set `readiness_probe.enabled` to `false`:
+
+
+```yaml
+readiness_probe:
+ enabled: false
+```
+
+Likewise, to disable the liveness `Mapping` entirely, set `liveness_probe.enabled` to `false`:
+
+
+```yaml
+liveness_probe:
+ enabled: false
+```
+
+A disabled probe endpoint will respond with 404; however, the service is still running, and will be accessible on localhost port 8877 from inside the $productName$ Pod.
+
+You can change these to route requests to some other service. For example, to have the readiness probe map to the `quote` application's health check:
+
+```yaml
+readiness_probe:
+ enabled: true
+ service: quote
+ rewrite: /backend/health
+```
+
+The liveness and readiness probes both support `prefix` and `rewrite`, with the same meanings as for [Mappings](../../using/mappings).
+
+##### Retry policy
+
+This lets you add resilience to your services in case of request failures by performing automatic retries.
+
+```yaml
+retry_policy:
+ retry_on: "5xx"
+```
+
+---
+
+## Traffic management
+
+##### Circuit breaking
+
+* `circuit_breakers` sets the global circuit breaking configuration defaults
+
+You can override the circuit breaker settings for individual `Mapping`s. By default, $productName$ does not configure any circuit breakers. For more information, see the [circuit breaking reference](../../using/circuit-breakers).
+
+##### Default label domain and labels
+
+* `default_labels` sets default domains and labels to apply to every request.
+
+For more on how to use the default labels, , see the [Rate Limit reference](../../using/rate-limits/#attaching-labels-to-requests).
+
+##### Default load balancer
+
+* `load_balancer` sets the default load balancing type and policy
+
+For example, to set the default load balancer to `least_request`:
+
+```yaml
+load_balancer:
+ policy: least_request
+```
+
+If not set, the default is to use round-robin load balancing. For more information, see the [load balancer reference](../load-balancer).
diff --git a/docs/edge-stack/3.9/topics/running/debugging.md b/docs/edge-stack/3.9/topics/running/debugging.md
new file mode 100644
index 000000000..8f62c7239
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/debugging.md
@@ -0,0 +1,203 @@
+# Debugging
+
+If you’re experiencing issues with the $productName$ and cannot diagnose the issue through the `/ambassador/v0/diag/` diagnostics endpoint, this document covers various approaches and advanced use cases for debugging $productName$ issues.
+
+We assume that you already have a running $productName$ installation in the following sections.
+
+## A Note on TLS
+
+[TLS] can appear intractable if you haven't set up [certificates] correctly. If you're
+having trouble with TLS, always [check the logs] of your $productName$ Pods and look for
+certificate errors.
+
+[tls]: ../tls
+[certificates]: ../tls#certificates-and-secrets
+[check the logs]: #review-logs
+
+## Check $productName$ status
+
+1. First, check the $productName$ Deployment with the following: `kubectl get -n $productNamespace$ deployments`
+
+ After a brief period, the terminal will print something similar to the following:
+
+ ```
+ $ kubectl get -n $productNamespace$ deployments
+ NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
+ $productDeploymentName$ 3 3 3 3 1m
+ $productDeploymentName$-apiext 3 3 3 3 1m
+ ```
+
+2. Check that the “desired” number of Pods matches the “current” and “available” number of Pods.
+
+3. If they are **not** equal, check the status of the associated Pods with the following command: `kubectl get pods -n $productNamespace$`.
+
+ The terminal should print something similar to the following:
+
+ ```
+ $ kubectl get pods -n $productNamespace$
+ NAME READY STATUS RESTARTS AGE
+ $productDeploymentName$-85c4cf67b-4pfj2 1/1 Running 0 1m
+ $productDeploymentName$-85c4cf67b-fqp9g 1/1 Running 0 1m
+ $productDeploymentName$-85c4cf67b-vg6p5 1/1 Running 0 1m
+ $productDeploymentName$-apiext-736f8497d-j34pf 1/1 Running 0 1m
+ $productDeploymentName$-apiext-736f8497d-9gfpq 1/1 Running 0 1m
+ $productDeploymentName$-apiext-736f8497d-p5wgx 1/1 Running 0 1m
+ ```
+
+ The actual names of the Pods will vary. All the Pods should indicate `Running`, and all should show 1/1 containers ready.
+
+4. If the Pods do not seem reasonable, use the following command for details about the history of the Deployment: `kubectl describe -n $productNamespace$ deployment $productDeploymentName$`
+
+ - Look for data in the “Replicas” field near the top of the output. For example:
+ `Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable`
+
+ - Look for data in the “Events” log field near the bottom of the output, which often displays data such as a fail image pull, RBAC issues, or a lack of cluster resources. For example:
+
+ ```
+ Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set $productDeploymentName$-85c4cf67b to 3
+ ```
+
+5. Additionally, use the following command to “describe” the individual Pods: `kubectl describe pods -n $productNamespace$ <$productDeploymentName$-pod-name>`
+
+ - Look for data in the “Status” field near the top of the output. For example, `Status: Running`
+
+ - Look for data in the “Events” field near the bottom of the output, as it will often show issues such as image pull failures, volume mount issues, and container crash loops. For example:
+ ```
+ Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal Scheduled 4m default-scheduler Successfully assigned $productDeploymentName$-85c4cf67b-4pfj2 to gke-ambassador-demo-default-pool-912378e5-dkxc
+ Normal SuccessfulMountVolume 4m kubelet, gke-ambassador-demo-default-pool-912378e5-dkxc MountVolume.SetUp succeeded for volume "$productDeploymentName$-token-tmk94"
+ Normal Pulling 4m kubelet, gke-ambassador-demo-default-pool-912378e5-dkxc pulling image "docker.io/datawire/ambassador:0.40.0"
+ Normal Pulled 4m kubelet, gke-$productDeploymentName$-demo-default-pool-912378e5-dkxc Successfully pulled image "docker.io/datawire/ambassador:0.40.0"
+ Normal Created 4m kubelet, gke-$productDeploymentName$-demo-default-pool-912378e5-dkxc Created container
+ Normal Started 4m kubelet, gke-$productDeploymentName$-demo-default-pool-912378e5-dkxc Started container
+ ```
+
+In both the Deployment Pod and the individual Pods, take the necessary action to address any discovered issues.
+
+
Review $productName$ logs
+
+$productName$ logging can provide information on anything that might be abnormal or malfunctioning. While there may be a large amount of data to sort through, look for key errors such as the $productName$ process restarting unexpectedly, or a malformed Envoy configuration.
+
+$productName$ has two major log mechanisms: $productName$ logging and Envoy logging. Both appear in the normal `kubectl logs` output, and both can have additional debug-level logging enabled.
+
+
+ Enabling debug-level logging can produce a lot of log output — enough to
+ potentially impact the performance of $productName$. We don't recommend running with debug
+ logging enabled as a matter of course; it's usually better to enable it only when needed,
+ then reset logging to normal once you're finished debugging.
+
+
+### $productName$ debug logging
+
+Much of $productName$'s logging is concerned with the business of noticing changes to
+Kubernetes resources that specify the $productName$ configuration, and generating new
+Envoy configuration in response to those changes. $productName$ also logs information
+about its built-in authentication, rate limiting, developer portal, ACME, etc. There
+are multiple environment variables controlling debug logging; which is required depends
+on which aspect of the system you want to debug:
+
+- Set `AES_LOG_LEVEL=debug` to debug the early boot sequence, $productName$'s interactions
+ with the Kubernetes cluster (finding changed resources, etc.), and the built-in services
+ (auth, rate limiting, etc.).
+- Set `AMBASSADOR_DEBUG=diagd` to debug the process of generating an Envoy configuration from
+ the input resources.
+
+### $productName$ Envoy logging
+
+Envoy logging is concerned with the actions Envoy is taking for incoming requests.
+Typically, Envoy will only output access logs, and certain errors, but enabling Envoy
+debug logging will show very verbose information about the actions Envoy is actually
+taking. It can be useful for understanding why connections are being closed, or whether
+an error status is coming from Envoy or from the upstream service.
+
+It is possible to enable Envoy logging at boot, but for the most part, it's safer to
+enable it at runtime, right before sending a request that is known to have problems.
+To enable Envoy debug logging, use `kubectl exec` to get a shell on the $productName$
+pod, then:
+
+ ```
+ curl -XPOST http://localhost:8001/logging?level=trace && \
+ sleep 10 && \
+ curl -XPOST http://localhost:8001/logging?level=warning
+ ```
+
+This will turn on Envoy debug logging for ten seconds, then turn it off again.
+
+#### Interpreting Response Codes
+
+Envoys default access log format includes the `%RESPONSE_FLAGS%` which provides additional information about the response or connection that can help with debugging issues.
+
+For example, if a log line includes `UAEX` then this indicates that an Edge Stack Filter has denied the request. This can occur because a user was not authenticated or because of an error. Therefore, this can indicate that further investigation of the logs is needed.
+
+See Envoy's documentation for a full list of the supported [%RESPONSE_FLAGS%](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators).
+
+### Viewing logs
+
+To view the logs from $productName$:
+
+1. Use the following command to target an individual $productName$ Pod: `kubectl get pods -n $productNamespace$`
+
+ The terminal will print something similar to the following:
+
+ ```
+ $ kubectl get pods -n $productNamespace$
+ NAME READY STATUS RESTARTS AGE
+ $productDeploymentName$-85c4cf67b-4pfj2 1/1 Running 0 3m
+ ```
+
+2. Then, run the following: `kubectl logs -n $productNamespace$ <$productDeploymentName$-pod-name>`
+
+The terminal will print something similar to the following:
+
+ ```
+ $ kubectl logs -n $productNamespace$ $productDeploymentName$-85c4cf67b-4pfj2
+ 2018-10-10 12:26:50 kubewatch 0.40.0 INFO: generating config with gencount 1 (0 changes)
+ /usr/lib/python3.6/site-packages/pkg_resources/__init__.py:1235: UserWarning: /ambassador is writable by group/others and vulnerable to attack when used with get_resource_filename. Consider a more secure location (set with .set_extraction_path or the PYTHON_EGG_CACHE environment variable).
+ warnings.warn(msg, UserWarning)
+ 2018-10-10 12:26:51 kubewatch 0.40.0 INFO: Scout reports {"latest_version": "0.40.0", "application": "ambassador", "notices": [], "cached": false, "timestamp": 1539606411.061929}
+
+ 2018-10-10 12:26:54 diagd 0.40.0 [P15TMainThread] INFO: thread count 3, listening on 0.0.0.0:8877
+ [2018-10-10 12:26:54 +0000] [15] [INFO] Starting gunicorn 19.8.1
+ [2018-10-10 12:26:54 +0000] [15] [INFO] Listening at: http://0.0.0.0:8877 (15)
+ [2018-10-10 12:26:54 +0000] [15] [INFO] Using worker: threads
+ [2018-10-10 12:26:54 +0000] [42] [INFO] Booting worker with pid: 42
+ 2018-10-10 12:26:54 diagd 0.40.0 [P42TMainThread] INFO: Starting periodic updates
+ [2018-10-10 12:27:01.977][21][info][main] source/server/drain_manager_impl.cc:63] shutting down parent after drain
+ ```
+
+Note that many deployments will have multiple logs, and the logs are independent for each Pod.
+
+## Examine Pod and container contents
+
+You can examine the contents of the $productName$ Pod for issues, such as if volume mounts are correct and TLS certificates are present in the required directory, to determine if the Pod has the latest $productName$ configuration, or if the generated Envoy configuration is correct or as expected. In these instructions, we will look for problems related to the Envoy configuration.
+
+1. To look into an $productName$ Pod, get a shell on the Pod using `kubectl exec`. For example,
+
+ ```
+ kubectl exec -it -n $productNamespace$ <$productDeploymentName$-pod-name> -- bash
+ ```
+
+2. Determine the latest configuration. If you haven't overridden the configuration directory, the latest configuration will be in `/ambassador/snapshots`. If you have overridden it, $productName$ saves configurations in `$AMBASSADOR_CONFIG_BASE_DIR/snapshots`.
+
+ In the snapshots directory:
+
+ - `snapshot.yaml` contains the full input configuration that $productName$ has found;
+ - `aconf.json` contains the $productName$ configuration extracted from the snapshot;
+ - `ir.json` contains the IR constructed from the $productName$ configuration; and
+ - `econf.json` contains the Envoy configuration generated from the IR.
+
+ In the snapshots directory, the current configuration will be stored in files with no digit suffix, and older configurations have increasing numbers. For example, `ir.json` is current, `ir-1.json` is the next oldest, then `ir-2.json`, etc.
+
+3. If something is wrong with `snapshot` or `aconf`, there is an issue with your configuration. If something is wrong with `ir` or `econf`, you should [open an issue on Github](https://github.com/emissary-ingress/emissary/issues/new/choose).
+
+4. The actual input provided to Envoy is split into `$AMBASSADOR_CONFIG_BASE_DIR/bootstrap-ads.json` and `$AMBASSADOR_CONFIG_BASE_DIR/envoy/envoy.json`.
+
+ - The `bootstrap-ads.json` file contains details about Envoy statistics, logging, authentication, etc.
+ - The `envoy.json` file contains information about request routing.
+ - You may generally find it simplest to just look at the `econf.json` files in the `snapshot`
+ directory, which includes both kinds of configuration.
diff --git a/docs/edge-stack/3.9/topics/running/environment.md b/docs/edge-stack/3.9/topics/running/environment.md
new file mode 100644
index 000000000..54a05c9bb
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/environment.md
@@ -0,0 +1,751 @@
+# $productName$ Environment variables
+
+Use the following variables for the environment of your $productName$ container:
+
+| Variable | Default value | Value type |
+|----------------------------------------------------------------------------------------------------------- |-----------------------------------------------------|-------------------------------------------------------------------------------|
+| [`AMBASSADOR_ID`](#ambassador_id) | `[ "default" ]` | List of strings |
+| [`AES_LOG_LEVEL`](#aes_log_level)
+| [`AES_DISABLE_LICENSE_USAGE_REPORTING`](#aes_disable_license_usage_reporting) | `false` | Any: `true`=true and unset is false |
+| [`AGENT_CONFIG_RESOURCE_NAME`](#agent_config_resource_name) | `ambassador-agent-cloud-token` | String |
+| [`AMBASSADOR_AMBEX_NO_RATELIMIT`](#ambassador_ambex_no_ratelimit) | `false` | Boolean: `true`=true, any other value=false |
+| [`AMBASSADOR_AMBEX_SNAPSHOT_COUNT`](#ambassador_ambex_snapshot_count) | `30` | Integer |
+| [`AMBASSADOR_CLUSTER_ID`](#ambassador_cluster_id) | Empty | String |
+| [`AMBASSADOR_CONFIG_BASE_DIR`](#ambassador_config_base_dir) | `/ambassador` | String |
+| [`AMBASSADOR_DISABLE_FEATURES`](#ambassador_disable_features) | Empty | Any |
+| [`AMBASSADOR_DRAIN_TIME`](#ambassador_drain_time) | `600` | Integer |
+| [`AMBASSADOR_ENVOY_API_VERSION`](#ambassador_envoy_api_version) | `V3` | String Enum; `V3` or `V2` |
+| [`AMBASSADOR_GRPC_METRICS_SINK`](#ambassador_grpc_metrics_sink) | Empty | String (address:port) |
+| [`AMBASSADOR_HEALTHCHECK_BIND_ADDRESS`](#ambassador_healthcheck_bind_address)| `0.0.0.0` | String |
+| [`AMBASSADOR_HEALTHCHECK_BIND_PORT`](#ambassador_healthcheck_bind_port)| `8877` | Integer |
+| [`AMBASSADOR_HEALTHCHECK_IP_FAMILY`](#ambassador_healthcheck_ip_family)| `ANY` | String Enum; `IPV4_ONLY` or `IPV6_ONLY`|
+| [`AMBASSADOR_ISTIO_SECRET_DIR`](#ambassador_istio_secret_dir) | `/etc/istio-certs` | String |
+| [`AMBASSADOR_JSON_LOGGING`](#ambassador_json_logging) | `false` | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_READY_PORT`](#ambassador_ready_port) | `8006` | Integer |
+| [`AMBASSADOR_READY_LOG`](#ambassador_ready_log) | `false` | Boolean; [Go `strconv.ParseBool`] |
+| [`AMBASSADOR_LABEL_SELECTOR`](#ambassador_label_selector) | Empty | String (label=value) |
+| [`AMBASSADOR_NAMESPACE`](#ambassador_namespace) | `default` ([^1]) | Kubernetes namespace |
+| [`AMBASSADOR_RECONFIG_MAX_DELAY`](#ambassador_reconfig_max_delay) | `1` | Integer |
+| [`AMBASSADOR_SINGLE_NAMESPACE`](#ambassador_single_namespace) | Empty | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_SNAPSHOT_COUNT`](#ambassador_snapshot_count) | `4` | Integer |
+| [`AMBASSADOR_VERIFY_SSL_FALSE`](#ambassador_verify_ssl_false) | `false` | Boolean; `true`=true, any other value=false |
+| [`DD_ENTITY_ID`](#dd_entity_id) | Empty | String |
+| [`DOGSTATSD`](#dogstatsd) | `false` | Boolean; Python `value.lower() == "true"` |
+| [`SCOUT_DISABLE`](#scout_disable) | `false` | Boolean; `false`=false, any other value=true |
+| [`STATSD_ENABLED`](#statsd_enabled) | `false` | Boolean; Python `value.lower() == "true"` |
+| [`STATSD_PORT`](#statsd_port) | `8125` | Integer |
+| [`STATSD_HOST`](#statsd_host) | `statsd-sink` | String |
+| [`STATSD_FLUSH_INTERVAL`](#statsd_flush_interval) | `1` | Integer |
+| [`_AMBASSADOR_ID`](#_ambassador_id) | Empty | String |
+| [`_AMBASSADOR_TLS_SECRET_NAME`](#_ambassador_tls_secret_name) | Empty | String |
+| [`_AMBASSADOR_TLS_SECRET_NAMESPACE`](#_ambassador_tls_secret_namespace) | Empty | String |
+| [`_CONSUL_HOST`](#_consul_host) | Empty | String |
+| [`_CONSUL_PORT`](#_consul_port) | Empty | Integer |
+| [`AMBASSADOR_DISABLE_SNAPSHOT_SERVER`](#ambassador_disable_snapshot_server) | `false` | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_ENVOY_BASE_ID`](#ambassador_envoy_base_id) | `0` | Integer | | `false` | Boolean; Python `value.lower() == "true"` |
+| [`AES_RATELIMIT_PREVIEW`](#aes_ratelimit_preview) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`AES_AUTH_TIMEOUT`](#aes_auth_timeout) | `4s` | Duration; [Go `time.ParseDuration`][]
+| [`REDIS_SOCKET_TYPE`](#redis_socket_type) | `tcp` | Go network such as `tcp` or `unix`; see [Go `net.Dial`][] |
+| [`REDIS_URL`](#redis_url) | None, must be set explicitly | Go network address; for TCP this is a `host:port` pair; see [Go `net.Dial`][] |
+| [`REDIS_TLS_ENABLED`](#redis_tls_enabled) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_TLS_INSECURE`](#redis_tls_insecure) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_USERNAME`](#redis_username) | Empty | Plain string |
+| [`REDIS_PASSWORD`](#redis_password) | Empty | Plain string |
+| [`REDIS_AUTH`](#redis_auth) | Empty | Requires AES_RATELIMIT_PREVIEW; Plain string |
+| [`REDIS_POOL_SIZE`](#redis_pool_size) | `10` | Integer |
+| [`REDIS_PING_INTERVAL`](#redis_ping_interval) | `10s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_TIMEOUT`](#redis_timeout) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_SURGE_LIMIT_INTERVAL`](#redis_surge_limit_interval) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_SURGE_LIMIT_AFTER`](#redis_surge_limit_after) | The value of `REDIS_POOL_SIZE` | Integer |
+| [`REDIS_SURGE_POOL_SIZE`](#redis_surge_pool_size) | `0` | Integer |
+| [`REDIS_SURGE_POOL_DRAIN_INTERVAL`](#redis_surge_pool_drain_interval) | `1m` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PIPELINE_WINDOW`](#redis_pipeline_window) | `0` | Requires AES_RATELIMIT_PREVIEW; Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PIPELINE_LIMIT`](#redis_pipeline_limit) | `0` | Requires AES_RATELIMIT_PREVIEW; Integer; [Go `strconv.ParseInt`][] |
+| [`REDIS_TYPE`](#redis_type) | `SINGLE` | Requires AES_RATELIMIT_PREVIEW; String; SINGLE, SENTINEL, or CLUSTER |
+| [`REDIS_PERSECOND`](#redis_persecond) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_PERSECOND_SOCKET_TYPE`](#redis_persecond_socket_type) | None, must be set explicitly (if `REDIS_PERSECOND`) | Go network such as `tcp` or `unix`; see [Go `net.Dial`][] |
+| [`REDIS_PERSECOND_URL`](#redis_persecond_url) | None, must be set explicitly (if `REDIS_PERSECOND`) | Go network address; for TCP this is a `host:port` pair; see [Go `net.Dial`][] |
+| [`REDIS_PERSECOND_TLS_ENABLED`](#redis_persecond_tls_enabled) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_PERSECOND_TLS_INSECURE`](#redis_persecond_tls_insecure) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_PERSECOND_USERNAME`](#redis_persecond_username) | Empty | Plain string |
+| [`REDIS_PERSECOND_PASSWORD`](#redis_persecond_password) | Empty | Plain string |
+| [`REDIS_PERSECOND_AUTH`](#redis_persecond_auth) | Empty | Requires AES_RATELIMIT_PREVIEW; Plain string |
+| [`REDIS_PERSECOND_POOL_SIZE`](#redis_persecond_pool_size) | `10` | Integer |
+| [`REDIS_PERSECOND_PING_INTERVAL`](#redis_persecond_ping_interval) | `10s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_TIMEOUT`](#redis_persecond_timeout) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_SURGE_LIMIT_INTERVAL`](#redis_persecond_surge_limit_interval) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_SURGE_LIMIT_AFTER`](#redis_persecond_surge_limit_after) | The value of `REDIS_PERSECOND_POOL_SIZE` | Integer |
+| [`REDIS_PERSECOND_SURGE_POOL_SIZE`](#redis_persecond_surge_pool_size) | `0` | Integer |
+| [`REDIS_PERSECOND_SURGE_POOL_DRAIN_INTERVAL`](#redis_persecond_surge_pool_drain_interval) | `1m` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_TYPE`](#redis_persecond_type) | `SINGLE` | Requires AES_RATELIMIT_PREVIEW; String; SINGLE, SENTINEL, or CLUSTER |
+| [`REDIS_PERSECOND_PIPELINE_WINDOW`](#redis_persecond_pipeline_window) | `0` | Requires AES_RATELIMIT_PREVIEW; Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_PIPELINE_LIMIT`](#redis_persecond_pipeline_limit) | `0` | Requires AES_RATELIMIT_PREVIEW; Integer |
+| [`EXPIRATION_JITTER_MAX_SECONDS`](#expiration_jitter_max_seconds) | `300` | Integer |
+| [`USE_STATSD`](#use_statsd) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`STATSD_HOST`](#statsd_host) | `localhost` | Hostname |
+| [`STATSD_PORT`](#statsd_port) | `8125` | Integer |
+| [`GOSTATS_FLUSH_INTERVAL_SECONDS`](#gostats_flush_interval_seconds) | `5` | Integer |
+| [`LOCAL_CACHE_SIZE_IN_BYTES`](#local_cache_size_in_bytes) | `0` | Requires AES_RATELIMIT_PREVIEW; Integer |
+| [`NEAR_LIMIT_RATIO`](#near_limit_ratio) | `0.8` | Requires AES_RATELIMIT_PREVIEW; Float; [Go `strconv.ParseFloat`][] || Developer Portal | `AMBASSADOR_URL` | `https://api.example.com` | URL |
+| [`DEVPORTAL_CONTENT_URL`](#devportal_content_url) | `https://github.com/datawire/devportal-content` | git-remote URL |
+| [`DEVPORTAL_CONTENT_DIR`](#devportal_content_dir) | `/` | Rooted Git directory |
+| [`DEVPORTAL_CONTENT_BRANCH`](#devportal_content_branch) | `master` | Git branch name |
+| [`DEVPORTAL_DOCS_BASE_PATH`](#devportal_docs_base_path) | `/doc/` | Git branch name |
+| [`POLL_EVERY_SECS`](#poll_every_secs) | `60` | Integer |
+| [`AES_ACME_LEADER_DISABLE`](#aes_acme_leader_disable) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`AES_REPORT_DIAGNOSTICS_TO_CLOUD`](#aes_report_diagnostics_to_cloud) | `true` | Boolean; [Go `strconv.ParseBool`][] |
+| [`AES_SNAPSHOT_URL`](#aes_snapshot_url) | `http://emissary-ingress-admin.default:8005/snapshot-external` | hostname |
+| [`ENV_AES_SECRET_NAME`](#env_aes_secret_name) | `ambassador-edge-stack` | String |
+| [`ENV_AES_SECRET_NAMESPACE`](#env_aes_secret_namespace) | $productName$'s Namespace | String |
+
+
+## Feature Flag Environment Variables
+
+| Variable | Default value | Value type |
+|----------------------------------------------------------------------------------------- |-----------------------------------------------------|-------------------------------------------------------------------------------|
+| [`AMBASSADOR_EDS_BYPASS`](#ambassador_eds_bypass) | `false` | Boolean; Python `value.lower() == "true"` |
+| [`AMBASSADOR_FORCE_SECRET_VALIDATION`](#ambassador_force_secret_validation) | `false` | Boolean: `true`=true, any other value=false |
+| [`AMBASSADOR_KNATIVE_SUPPORT`](#ambassador_knative_support) | `false` | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_UPDATE_MAPPING_STATUS`](#ambassador_update_mapping_status) | `false` | Boolean; `true`=true, any other value=false |
+| [`ENVOY_CONCURRENCY`](#envoy_concurrency) | Empty | Integer |
+| [`DISABLE_STRICT_LABEL_SELECTORS`](#disable_strict_label_selectors) | `false` | Boolean: `true`=true, any other value=false |
+
+### `AMBASSADOR_ID`
+
+$productName$ supports running multiple installs in the same cluster without restricting a given instance of $productName$ to a single namespace.
+The resources that are visible to an installation can be limited with the `AMBASSADOR_ID` environment variable.
+
+[More information](../../running/running#ambassador_id)
+
+### `AES_LOG_LEVEL`
+
+Adjust the log level by setting the `AES_LOG_LEVEL` environment variable; from least verbose to most verbose, the valid values are `error`, `warn`/`warning`, `info`, `debug`, and `trace`. The default is `info`.
+Log level names are case-insensitive.
+
+[More information](../../running/running#log-levels-and-debugging)
+
+### `AES_DISABLE_LICENSE_USAGE_REPORTING`
+
+Usage data is collected and sent to Ambassador Labs servers on regular intervals. If you are using an in-cluster AirGapped license and wish to disable this, then setting `AES_DISABLE_LICENSE_USAGE_REPORTING` to true will prevent $productName$ from reporting license usage data. If your cluster is using a Cloud license then users are required to send this data and cannot disable it.
+
+[More license information](../../../topics/using/licenses)
+
+### `AGENT_CONFIG_RESOURCE_NAME`
+
+Allows overriding the default config_map/secret that is used for extracting the CloudToken for connecting with Ambassador cloud. It allows all
+components (and not only the Ambassador Agent) to authenticate requests to Ambassador Cloud.
+If unset it will just fallback to searching for a config map or secret with the name of `ambassador-agent-cloud-token`. Note: the secret will take precedence if both a secret and config map are set.
+
+### `AMBASSADOR_AMBEX_NO_RATELIMIT`
+
+Completely disables ratelimiting Envoy reconfiguration under memory pressure. This can help performance with the endpoint or Consul resolvers, but could make OOMkills more likely with large configurations.
+The default is `false`, meaning that the rate limiter is active.
+
+[More information](../../../topics/concepts/rate-limiting-at-the-edge/)
+
+### `AMBASSADOR_AMBEX_SNAPSHOT_COUNT`
+
+Envoy-configuration snapshots get saved (as `ambex-#.json`) in `/ambassador/snapshots`. The number of snapshots is controlled by the `AMBASSADOR_AMBEX_SNAPSHOT_COUNT` environment variable.
+Set it to 0 to disable.
+
+[More information](../../running/debugging#examine-pod-and-container-contents)
+
+### `AMBASSADOR_CLUSTER_ID`
+
+Each $productName$ installation generates a unique cluster ID based on the UID of its Kubernetes namespace and its $productName$ ID: the resulting cluster ID is a UUID which cannot be used
+to reveal the namespace name nor $productName$ ID itself. $productName$ needs RBAC permission to get namespaces for this purpose, as shown in the default YAML files provided by Datawire;
+if not granted this permission it will generate a UUID based only on the $productName$ ID. To disable cluster ID generation entirely, set the environment variable
+`AMBASSADOR_CLUSTER_ID` to a UUID that will be used for the cluster ID.
+
+[More information](../../running/running#ambassador-edge-stack-usage-telemetry-scout)
+
+### `AMBASSADOR_CONFIG_BASE_DIR`
+
+Controls where $productName$ will store snapshots. By default, the latest configuration will be in `/ambassador/snapshots`. If you have overridden it, $productName$ saves configurations in `$AMBASSADOR_CONFIG_BASE_DIR/snapshots`.
+
+[More information](../../running/debugging#examine-pod-and-container-contents)
+
+### `AMBASSADOR_DISABLE_FEATURES`
+
+To completely disable feature reporting, set the environment variable `AMBASSADOR_DISABLE_FEATURES` to any non-empty value.
+
+[More information](../../running/running/#ambassador-edge-stack-usage-telemetry-scout)
+
+### `AMBASSADOR_DRAIN_TIME`
+
+At each reconfiguration, $productName$ keeps around the old version of it's envoy config for the duration of the configured drain time.
+The `AMBASSADOR_DRAIN_TIME` variable controls how much of a grace period $productName$ provides active clients when reconfiguration happens.
+Its unit is seconds and it defaults to 600 (10 minutes). This can impact memory usage because $productName$ needs to keep around old versions of its configuration
+for the duration of the drain time.
+
+[More information](../../running/scaling#ambassador_drain_time)
+
+### `AMBASSADOR_ENVOY_API_VERSION`
+
+By default, $productName$ will configure Envoy using the [V3 Envoy API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/api).
+In $productName$ 2.0, you were able switch back to Envoy V2 by setting the `AMBASSADOR_ENVOY_API_VERSION` environment variable to "V2".
+$productName$ 3.0 has removed support for the V2 API and only the V3 API is used. While this variable cannot be set to another value in 3.0, it may
+be used when introducing new API versions that are not yet available in $productName$ such as V4.
+
+### `AMBASSADOR_GRPC_METRICS_SINK`
+
+Configures $productName$ (envoy) to send metrics to the Agent which are then relayed to the Cloud. If not set then we don’t configure envoy to send metrics to the agent. If set with a bad address:port then we log an error message. In either scenario, it just stops metrics from being sent to the Agent which has no negative effect on general routing or $productName$ uptime.
+
+### `AMBASSADOR_HEALTHCHECK_BIND_ADDRESS`
+
+Configures $productName$ to bind its health check server to the provided address. If not set $productName$ will bind to all addresses (`0.0.0.0`).
+
+### `AMBASSADOR_HEALTHCHECK_BIND_PORT`
+
+Configures $productName$ to bind its health check server to the provided port. If not set $productName$ will listen on the admin port(`8877`).
+
+### `AMBASSADOR_HEALTHCHECK_IP_FAMILY`
+
+Allows the IP Family used by health check server to be overriden. By default, the health check server will listen for both IPV4 and IPV6 addresses. In some clusters you may want to force `IPV4_ONLY` or `IPV6_ONLY`.
+
+### `AMBASSADOR_ISTIO_SECRET_DIR`
+
+$productName$ will read the mTLS certificates from `/etc/istio-certs` unless configured to use a different directory with the `AMBASSADOR_ISTIO_SECRET_DIR`
+environment variable and create a secret in that location named `istio-certs`.
+
+[More information](../../../howtos/istio#configure-an-mtls-tlscontext)
+
+### `AMBASSADOR_JSON_LOGGING`
+
+When `AMBASSADOR_JSON_LOGGING` is set to `true`, JSON format will be used for most of the control plane logs.
+Some (but few) logs from `gunicorn` and the Kubernetes `client-go` package will still be in text only format.
+
+[More information](../../running/running#log-format)
+
+### `AMBASSADOR_READY_PORT`
+
+A dedicated Listener is created for non-blocking readiness checks. By default, the Listener will listen on the loopback address
+and port `8006`. `8006` is part of the reserved ports dedicated to $productName$. If their is a conflict then setting
+`AMBASSADOR_READY_PORT` to a valid port will configure Envoy to Listen on that port.
+
+### `AMBASSADOR_READY_LOG`
+
+When `AMBASSADOR_READY_LOG` is set to `true`, the envoy `/ready` endpoint will be logged. It will honor format
+provided in the `Module` resource or default to the standard log line format.
+
+### `AMBASSADOR_LABEL_SELECTOR`
+
+Restricts $productName$'s configuration to only the labelled resources. For example, you could apply a `version-two: true` label
+to all resources that should be visible to $productName$, then set `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+Resources without the specified label will be ignored.
+
+### `AMBASSADOR_NAMESPACE`
+
+Controls namespace configuration for Amabssador.
+
+[More information](../../running/running#namespaces)
+
+### `AMBASSADOR_RECONFIG_MAX_DELAY`
+
+Controls up to how long Ambassador will wait to receive changes before doing an Envoy reconfiguration. The unit is
+in seconds and must be > 0.
+
+### `AMBASSADOR_SINGLE_NAMESPACE`
+
+When set, configures $productName$ to only work within a single namespace.
+
+[More information](../../running/running#namespaces)
+
+### `AMBASSADOR_SNAPSHOT_COUNT`
+
+The number of snapshots that $productName$ should save.
+
+### `AMBASSADOR_VERIFY_SSL_FALSE`
+
+By default, $productName$ will verify the TLS certificates provided by the Kubernetes API. In some situations, the cluster may be
+deployed with self-signed certificates. In this case, set `AMBASSADOR_VERIFY_SSL_FALSE` to `true` to disable verifying the TLS certificates.
+
+[More information](../../running/running#ambassador_verify_ssl_false)
+
+### `DD_ENTITY_ID`
+
+$productName$ supports setting the `dd.internal.entity_id` statitics tag using the `DD_ENTITY_ID` environment variable. If this value
+is set, statistics will be tagged with the value of the environment variable. Otherwise, this statistics tag will be omitted (the default).
+
+[More information](../../running/statistics/envoy-statsd#using-datadog-dogstatsd-as-the-statsd-sink)
+
+### `DOGSTATSD`
+
+If you are a user of the [Datadog](https://docs.datadoghq.com/) monitoring system, pulling in the Envoy statistics from $productName$ is very easy.
+Because the DogStatsD protocol is slightly different than the normal StatsD protocol, in addition to setting $productName$'s
+`STATSD_ENABLED=true` environment variable, you also need to set the`DOGSTATSD=true` environment variable.
+
+[More information](../../running/statistics/envoy-statsd#using-datadog-dogstatsd-as-the-statsd-sink)
+
+### `SCOUT_DISABLE`
+
+$productName$ integrates Scout, a service that periodically checks with Ambassador Labs servers to sends anonymized usage data and the $productName$ version. This information is important to us as we prioritize test coverage, bug fixes, and feature development. Note that the $productName$ will run regardless of the status of Scout.
+
+We do not recommend you disable Scout. This check can be disabled by setting
+the environment variable `SCOUT_DISABLE` to `1` in your $productName$ deployment.
+
+[More information](../../running/running#ambassador-edge-stack-usage-telemetry-scout)
+
+### `STATSD_ENABLED`
+
+If enabled, then $productName$ has Envoy expose metrics information via the ubiquitous and well-tested [StatsD](https://github.com/etsy/statsd)
+protocol. To enable this, you will simply need to set the environment variable `STATSD_ENABLED=true` in $productName$'s deployment YAML
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `STATSD_HOST`
+
+When this variable is set, $productName$ by default sends statistics to a Kubernetes service named `statsd-sink` on UDP port 8125 (the usual
+port of the StatsD protocol). You may instead tell $productName$ to send the statistics to a different StatsD server by setting the
+`STATSD_HOST` environment variable. This can be useful if you have an existing StatsD sink available in your cluster.
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `STATSD_PORT`
+
+Allows for configuring StatsD on a port other than the default (8125)
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `STATSD_FLUSH_INTERVAL`
+
+How often, in seconds, to submit statsd reports (if `STATSD_ENABLED`)
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `_AMBASSADOR_ID`
+
+Used with the Ambassador Consul connector. Sets the Ambassador ID so multiple instances of this integration can run per-Cluster when there are multiple $productNamePlural$ (Required if `AMBASSADOR_ID` is set in your $productName$ `Deployment`
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_AMBASSADOR_TLS_SECRET_NAME`
+
+Used with the Ambassador Consul connector. Sets the name of the Kubernetes `v1.Secret` created by this program that contains the Consul-generated TLS certificate.
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_AMBASSADOR_TLS_SECRET_NAMESPACE`
+
+Used with the Ambassador Consul connector. Sets the namespace of the Kubernetes `v1.Secret` created by this program.
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_CONSUL_HOST`
+
+Used with the Ambassador Consul connector. Sets the IP or DNS name of the target Consul HTTP API server
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_CONSUL_PORT`
+
+Used with the Ambassador Consul connector. Sets the port number of the target Consul HTTP API server.
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `AMBASSADOR_DISABLE_SNAPSHOT_SERVER`
+
+Disables the built-in snapshot server
+
+### `AMBASSADOR_ENVOY_BASE_ID`
+
+Base ID of the Envoy process
+
+### `AES_RATELIMIT_PREVIEW`
+
+Enables support for redis clustering, local caching, and an upgraded redis client with improved scalability in
+preview mode.
+
+[More information](../aes-redis/#aes_ratelimit_preview)
+
+### `AES_AUTH_TIMEOUT`
+
+Configures the default timeout in the authentication extension.
+
+[More information](../aes-extensions/authentication/#timeout-variables)
+
+### `REDIS_SOCKET_TYPE`
+
+Redis currently support three different deployment methods. $productName$ can now support using a Redis deployed in any of these ways for rate
+limiting when `AES_RATELIMIT_PREVIEW=true`.
+
+[More information](../aes-redis#socket_type)
+
+### `REDIS_URL`
+
+The URL to dial to talk to Redis.
+
+This will be either a hostname:port pair or a comma separated list of
+hostname:port pairs depending on the [`REDIS_TYPE`](#redis_type) you are using.
+
+[More information](../aes-redis#url)
+
+### `REDIS_TLS_ENABLED`
+
+Specifies whether to use TLS when talking to Redis.
+
+[More information](../aes-redis#tls_enabled)
+
+### `REDIS_TLS_INSECURE`
+
+Specifies whether to skip certificate verification when using TLS to talk to Redis.
+
+[More information](../aes-redis#tls_insecure)
+
+### `REDIS_USERNAME`
+
+`REDIS_USERNAME` and `REDIS_PASSWORD` handle all Redis authentication that is separate from Rate Limit Preview so failing to set them when using `REDIS_AUTH` will result in Ambassador not being able to authenticate with Redis for all of its other functionality.
+
+[More information](../aes-redis#username)
+
+### `REDIS_PASSWORD`
+
+`REDIS_USERNAME` and `REDIS_PASSWORD` handle all Redis authentication that is separate from Rate Limit Preview so failing to set them when using `REDIS_AUTH` will result in Ambassador not being able to authenticate with Redis for all of its other functionality.
+
+[More information](../aes-redis#password)
+
+### `REDIS_AUTH`
+
+If you configure `REDIS_AUTH`, then `REDIS_USERNAME` cannot be changed from the value `default`, and
+`REDIS_PASSWORD` should contain the same value as `REDIS_AUTH`.
+
+[More information](../aes-redis#auth)
+
+### `REDIS_POOL_SIZE`
+
+The number of connections to keep around when idle. The total number of connections may go lower than this if there are errors.
+The total number of connections may go higher than this during a load surge.
+
+[More information](../aes-redis#pool_size)
+
+### `REDIS_PING_INTERVAL`
+
+The rate at which Ambassador will ping the idle connections in the normal pool
+(not extra connections created for a load surge).
+
+[More information](../aes-redis#ping_interval)
+
+### `REDIS_TIMEOUT`
+
+Sets 4 different timeouts:
+
+ 1. `(*net.Dialer).Timeout` for establishing connections
+ 2. `(*redis.Client).ReadTimeout` for reading a single complete response
+ 3. `(*redis.Client).WriteTimeout` for writing a single complete request
+ 4. The timeout when waiting for a connection to become available from the pool (not including the dial time, which is timed out separately)
+
+A value of "0" means "no timeout".
+
+[More information](../aes-redis#timeout)
+
+### `REDIS_SURGE_LIMIT_INTERVAL`
+
+During a load surge, if the pool is depleted, then Ambassador may create new
+connections to Redis in order to fulfill demand, at a maximum rate of one new
+connection per `REDIS_SURGE_LIMIT_INTERVAL`.
+
+[More information](../aes-redis#surge_limit_interval)
+
+### `REDIS_SURGE_LIMIT_AFTER`
+
+The number of connections that can be created _after_ the normal pool is
+depleted before `REDIS_SURGE_LIMIT_INTERVAL` kicks in.
+
+[More information](../aes-redis#surge_limit_after)
+
+### `REDIS_SURGE_POOL_SIZE`
+
+Normally during a surge, excess connections beyond `REDIS_POOL_SIZE` are
+closed immediately after they are done being used, instead of being returned
+to a pool.
+
+`REDIS_SURGE_POOL_SIZE` configures a "reserve" pool for excess connections
+created during a surge.
+
+[More information](../aes-redis#surge_pool_size)
+
+### `REDIS_SURGE_POOL_DRAIN_INTERVAL`
+
+How quickly to drain connections from the surge pool after a surge is over.
+
+[More information](../aes-redis#surge_pool_drain_interval)
+
+### `REDIS_PIPELINE_WINDOW`
+
+The duration after which internal pipelines will be flushed.
+
+[More information](../aes-redis#pipeline_window)
+
+### `REDIS_PIPELINE_LIMIT`
+
+The maximum number of commands that can be pipelined before flushing.
+
+[More information](../aes-redis#pipeline_limit)
+
+### `REDIS_TYPE`
+
+Redis currently support three different deployment methods. $productName$ can now support using a Redis deployed in any of these ways for rate
+limiting when `AES_RATELIMIT_PREVIEW=true`.
+
+[More information](../aes-redis#type)
+
+### `REDIS_PERSECOND`
+
+If true, a second Redis connection pool is created (to a potentially different Redis instance) that is only used for per-second
+RateLimits; this second connection pool is configured by the `REDIS_PERSECOND_*` variables rather than the usual `REDIS_*` variables.
+
+[More information](../aes-redis#redis_persecond)
+
+### `REDIS_PERSECOND_SOCKET_TYPE`
+
+Configures the [REDIS_SOCKET_TYPE](#redis_socket_type) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#socket_type)
+
+### `REDIS_PERSECOND_URL`
+
+Configures the [REDIS_URL](#redis_url) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#url)
+
+### `REDIS_PERSECOND_TLS_ENABLED`
+
+Configures [REDIS_TLS_ENABLED](#redis_tls_enabled) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#tls_enabled)
+
+### `REDIS_PERSECOND_TLS_INSECURE`
+
+Configures [REDIS_TLS_INSECURE](#redis_tls_insecure) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#tls_insecure)
+
+### `REDIS_PERSECOND_USERNAME`
+
+Configures the [REDIS_USERNAME](#redis_username) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#username)
+
+### `REDIS_PERSECOND_PASSWORD`
+
+Configures the [#REDIS_PASSWORD](#redis_password) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#password)
+
+### `REDIS_PERSECOND_AUTH`
+
+Configures [REDIS_AUTH](#redis_auth) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#auth)
+
+### `REDIS_PERSECOND_POOL_SIZE`
+
+Configures the [REDIS_POOL_SIZE](#redis_pool_size) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#pool_size)
+
+### `REDIS_PERSECOND_PING_INTERVAL`
+
+Configures the [REDIS_PING_INTERVAL](#redis_ping_interval) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#ping_interval)
+
+### `REDIS_PERSECOND_TIMEOUT`
+
+Configures the [REDIS_TIMEOUT](#redis_timeout) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#timeout)
+
+### `REDIS_PERSECOND_SURGE_LIMIT_INTERVAL`
+
+Configures the [REDIS_SURGE_LIMIT_INTERVAL](#redis_surge_limit_interval) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_limit_interval)
+
+### `REDIS_PERSECOND_SURGE_LIMIT_AFTER`
+
+Configures [REDIS_SURGE_LIMIT_AFTER](#redis_surge_limit_after) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_limit_after)
+
+### `REDIS_PERSECOND_SURGE_POOL_SIZE`
+
+Configures the [REDIS_SURGE_POOL_SIZE](#redis_surge_pool_size) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_pool_size)
+
+### `REDIS_PERSECOND_SURGE_POOL_DRAIN_INTERVAL`
+
+Configures the [REDIS_SURGE_POOL_DRAIN_INTERVAL](#redis_surge_pool_drain_interval) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_pool_drain_interval)
+
+### `REDIS_PERSECOND_TYPE`
+
+Configures the [REDIS_TYPE](#redis_type) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#type)
+
+### `REDIS_PERSECOND_PIPELINE_WINDOW`
+
+Configures the [REDIS_PIPELINE_WINDOW](#redis_pipeline_window) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#pipeline_window)
+
+### `REDIS_PERSECOND_PIPELINE_LIMIT`
+
+Configures the [REDIS_PIPELING_LIMIT](#redis_pipeline_limit) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#pipeline_limit)
+
+### `EXPIRATION_JITTER_MAX_SECONDS`
+
+### `USE_STATSD`
+
+The `RateLimitService` reports to statsd, and attempts to do so by default (`USE_STATSD`, `STATSD_HOST`, `STATSD_PORT`, `GOSTATS_FLUSH_INTERVAL_SECONDS`).
+
+### `GOSTATS_FLUSH_INTERVAL_SECONDS`
+
+Configures the flush interval in seconds for the go statistics.
+
+### `LOCAL_CACHE_SIZE_IN_BYTES`
+
+Only available if `AES_RATELIMIT_PREVIEW: "true`. The AES rate limit extension can optionally cache over-the-limit keys so it does
+not need to read the redis cache again for requests with labels that are already over the limit.
+
+Setting `LOCAL_CACHE_SIZE_IN_BYTES` to a non-zero value with enable local caching.
+
+[More information](../aes-extensions/ratelimit#local_cache_size_in_bytes)
+
+### `NEAR_LIMIT_RATIO`
+
+Only available if `AES_RATELIMIT_PREVIEW: "true"`. Adjusts the ratio used by the `near_limit` statistic for tracking requests that
+are "near the limit". Defaults to `0.8` (80%) of the limit defined in the `RateLimit` rule.
+
+[More information](../aes-extensions/ratelimit#near_limit_ratio)
+
+### `DEVPORTAL_CONTENT_URL`
+
+Default URL to the repository hosting the content for the Portal
+
+[More information](../../using/dev-portal)
+
+### `DEVPORTAL_CONTENT_DIR`
+
+Default content subdirectory within the `DEVPORTAL_CONTENT_URL` the devportal content is located at (defaults to `/`)
+
+[More information](../../using/dev-portal)
+
+### `DEVPORTAL_CONTENT_BRANCH`
+
+Default content branch within the repo at `DEVPORTAL_CONTENT_URL` to use for the devportal content (defaults to `master`)
+
+[More information](../../using/dev-portal)
+
+### `DEVPORTAL_DOCS_BASE_PATH`
+
+Base path for each api doc (defaults to `/doc/`)
+
+[More information](../../using/dev-portal)
+
+### `POLL_EVERY_SECS`
+
+Interval for polling OpenAPI docs; default 60 seconds. Set to 0 to disable devportal polling.
+
+[More information](../../using/dev-portal)
+
+### `AES_ACME_LEADER_DISABLE`
+
+This prevents $productName$ from trying to manage ACME. When enabled, `Host` resources will be unable to use ACME to manage their tls secrets
+regardless of the config on the `Host` resource.
+
+### `AES_REPORT_DIAGNOSTICS_TO_CLOUD`
+
+By setting `AES_REPORT_DIAGNOSTICS_TO_CLOUD` to false, you can disable the feature where diagnostic information about your installation of $productName$
+will be sent to Ambassador cloud. If this variable is disabled, you will be unable to access cluster diagnostic information in the cloud.
+
+### `AES_SNAPSHOT_URL`
+
+Configures the default endpoint where config snapshots are stored and accessed.
+
+### `ENV_AES_SECRET_NAME`
+
+Use to override the name of the secret that $productName$ attempts to find licensing information in.
+
+### `ENV_AES_SECRET_NAMESPACE`
+
+Use to override the namespace of the secret that $productName$ attempts to find licensing information in.
+By default, $productName$ will look for the secret in the same namespace that $productName$ was installed in.
+
+### `AMBASSADOR_EDS_BYPASS`
+
+Bypasses EDS handling of endpoints and causes endpoints to be inserted to clusters manually. This can help resolve with `503 UH`
+caused by certification rotation relating to a delay between EDS + CDS.
+
+### `AMBASSADOR_FORCE_SECRET_VALIDATION`
+
+If you set the `AMBASSADOR_FORCE_SECRET_VALIDATION` environment variable, invalid Secrets will be rejected,
+and a `Host` or `TLSContext` resource attempting to use an invalid certificate will be disabled entirely.
+
+[More information](../../running/tls#certificates-and-secrets)
+
+### `AMBASSADOR_KNATIVE_SUPPORT`
+
+Enables support for knative
+
+### `AMBASSADOR_UPDATE_MAPPING_STATUS`
+
+If `AMBASSADOR_UPDATE_MAPPING_STATUS` is set to the string `true`, $productName$ will update the `status` of every `Mapping`
+CRD that it accepts for its configuration. This has no effect on the proper functioning of $productName$ itself, and can be a
+performance burden on installations with many `Mapping`s. It has no effect for `Mapping`s stored as annotations.
+
+The default is `false`. We recommend leaving `AMBASSADOR_UPDATE_MAPPING_STATUS` turned off unless required for external systems.
+
+[More information](../../running/running#ambassador_update_mapping_status)
+
+### `ENVOY_CONCURRENCY`
+
+Configures the optional [--concurrency](https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-concurrency) command line option when launching Envoy.
+This controls the number of worker threads used to serve requests and can be used to fine-tune system resource usage.
+
+### `DISABLE_STRICT_LABEL_SELECTORS`
+
+In $productName$ version `3.2`, a bug with how `Hosts` are associated with `Mappings` was fixed and with how `Listeners` are associated with `Hosts`. The `mappingSelector`\\`selector` fields in `Hosts` and `Listeners` were not
+properly being enforced in prior versions. If any single label from the selector was matched then the resources would be associated with each other instead
+of requiring all labels in the selector to be present. Additonally, if the `hostname` of a `Mapping` matched the `hostname` of a `Host` then they would be associated
+regardless of the configuration of `mappingSelector`\\`selector`.
+
+In version `3.2` this bug was fixed and resources that configure a selector will only be associated if **all** labels required by the selector are present.
+This brings the `mappingSelector` and `selector` fields in-line with how label selectors are used throughout Kubernetes. To avoid unexpected behavior after the upgrade,
+add all labels that configured in any `mappingSelector`\\`selector` to `Mappings` you want to associate with the `Host` or the `Hosts` you want to be associated with the `Listener`. You can opt-out of this fix and return to the old
+association behavior by setting the environment variable `DISABLE_STRICT_LABEL_SELECTORS` to `"true"` (default: `"false"`). A future version of
+$productName$ may remove the ability to opt-out of this bugfix.
+
+> **Note:** The `mappingSelector` field is only configurable on `v3alpha1` CRDs. In the `v2` CRDs the equivalent field is `selector`.
+either `selector` or `mappingSelector` may be configured in the `v3alpha1` CRDs, but `selector` has been deprecated in favour of `mappingSelector`.
+
+See The [Host documentation](../../running/host-crd#controlling-association-with-mappings) for more information about `Host` / `Mapping` association.
+
+## Port assignments
+
+$productName$ uses the following ports to listen for HTTP/HTTPS traffic automatically via TCP:
+
+| Port | Process | Function |
+|------|----------|---------------------------------------------------------|
+| 8001 | envoy | Internal stats, logging, etc.; not exposed outside pod |
+| 8002 | watt | Internal watt snapshot access; not exposed outside pod |
+| 8003 | ambex | Internal ambex snapshot access; not exposed outside pod |
+| 8004 | diagd | Internal `diagd` access; not exposed outside pod |
+| 8005 | snapshot | Exposes a scrubbed $productName$ snapshot outside of the pod |
+| 8080 | envoy | Default HTTP service port |
+| 8443 | envoy | Default HTTPS service port |
+| 8877 | diagd | Direct access to diagnostics UI; provided by `busyambassador entrypoint` |
+
+[^1]: This may change in a future release to reflect the Pods's
+ namespace if deployed to a namespace other than `default`.
+ https://github.com/emissary-ingress/emissary/issues/1583
+
+[Go `net.Dial`]: https://golang.org/pkg/net/#Dial
+[Go `strconv.ParseBool`]: https://golang.org/pkg/strconv/#ParseBool
+[Go `time.ParseDuration`]: https://pkg.go.dev/time#ParseDuration
+[Redis 6 ACL]: https://redis.io/topics/acl
diff --git a/docs/edge-stack/3.9/topics/running/host-crd.md b/docs/edge-stack/3.9/topics/running/host-crd.md
new file mode 100644
index 000000000..082a5f9d1
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/host-crd.md
@@ -0,0 +1,329 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Host** CRD
+
+The custom `Host` resource defines how $productName$ will be
+visible to the outside world. It collects all the following information in a
+single configuration resource:
+
+- The hostname by which $productName$ will be reachable
+- How $productName$ should handle TLS certificates
+- How $productName$ should handle secure and insecure requests
+- Which `Mappings` should be associated with this `Host`
+
+
+ Remember that Listener resources are required for a functioning
+ $productName$ installation!
+ Learn more about Listener.
+
+
+
+ Remember than $productName$ does not make sure that a wildcard Host exists! If the
+ wildcard behavior is needed, a Host with a hostname of "*"
+ must be defined by the user.
+
+
+A minimal `Host` resource, using Let’s Encrypt to handle TLS, would be:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: minimal-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ email: julian@example.com
+```
+
+This `Host` tells $productName$ to expect to be reached at `host.example.com`,
+and to manage TLS certificates using Let’s Encrypt, registering as
+`julian@example.com`. Since it doesn’t specify otherwise, requests using
+cleartext will be automatically redirected to use HTTPS, and $productName$ will
+not search for any specific further configuration resources related to this
+`Host`.
+
+Remember that a Listener will also be required for this example to
+be functional. Many examples of setting up `Host` and `Listener` are available in the
+[Configuring $productName$ to Communicate](../../../howtos/configure-communications) document.
+
+## Setting the `hostname`
+
+The `hostname` element tells $productName$ which hostnames to expect. `hostname` is a DNS glob,
+so all of the following are valid:
+
+- `host.example.com`
+- `*.example.com`
+- `host.example.*`
+
+The following are _not_ valid:
+
+- `host.*.com` -- Envoy supports only prefix and suffix globs
+- `*host.example.com` -- the wildcard must be its own element in the DNS name
+
+In all cases, the `hostname` is used to match the `:authority` header for HTTP routing.
+When TLS termination is active, the `hostname` is also used for SNI matching.
+
+## Controlling Association with `Mapping`s
+
+A `Mapping` will not be associated with a `Host` unless at least one of the following is true:
+
+- The `Mapping` specifies a `hostname` attribute that matches the `Host` in question.
+- The `Host` specifies a `mappingSelector` that matches the `Mapping`'s Kubernetes `label`s.
+
+> **Note:** The `mappingSelector` field is only configurable on `v3alpha1` CRDs. In the `v2` CRDs the equivalent field is `selector`.
+either `selector` or `mappingSelector` may be configured in the `v3alpha1` CRDs, but `selector` has been deprecated in favour of `mappingSelector`.
+
+If neither of the above is true, the `Mapping` will not be associated with the `Host` in
+question. This is intended to help manage memory consumption with large numbers of `Host`s and large
+numbers of `Mapping`s.
+
+If the `Host` specifies `mappingSelector` _and_ the `Mapping` specifies `hostname`, both must match
+for the association to happen.
+
+The `mappingSelector` is a Kubernetes [label selector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#labelselector-v1-meta). For a `Mapping` to be associated with a `Host` that uses `mappingSelector`, then **all** labels
+required by the `mappingSelector` must be present on the `Mapping` in order for it to be associated with the `Host`.
+A `Mapping` may have additional labels other than those required by the `mappingSelector` so long as the required labels are present.
+
+**in 2.0, only `matchLabels` is supported**, for example:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: minimal-host
+spec:
+ hostname: host.example.com
+ mappingSelector:
+ matchLabels:
+ examplehost: host
+```
+
+The above `Host` will associate with these `Mapping`s:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-label-match
+ labels:
+ examplehost: host # This matches the Host's mappingSelector.
+spec:
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-hostname-match
+spec:
+ hostname: host.example.com # This is an exact match of the Host's hostname.
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-hostname-glob-match
+spec:
+ hostname: '*.example.com' # This glob matches the Host's hostname too.
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-both-matches
+ labels:
+ examplehost: host # This matches the Host's mappingSelector.
+spec:
+ hostname: '*.example.com' # This glob matches the Host's hostname.
+ prefix: /httpbin/
+ service: http://httpbin.org
+```
+
+It will _not_ associate with any of these:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: skip-mapping-wrong-label
+ labels:
+ examplehost: staging # This doesn't match the Host's mappingSelector.
+spec:
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: skip-mapping-wrong-hostname
+spec:
+ hosname: 'bad.example.com' # This doesn't match the Host's hostname.
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: skip-mapping-still-wrong
+ labels:
+ examplehost: staging # This doesn't match the Host's mappingSelector,
+spec: # and if the Host specifies mappingSelector AND the
+ hostname: host.example.com # Mapping specifies hostname, BOTH must match. So
+ prefix: /httpbin/ # the matching hostname isn't good enough.
+ service: http://httpbin.org
+```
+
+Future versions of $productName$ will support `matchExpressions` as well.
+
+> **Note:** In $productName$ version `3.2`, a bug with how `Hosts` are associated with `Mappings` was fixed. The `mappingSelector` field in `Hosts` was not
+properly being enforced in prior versions. If any single label from the selector was matched then the `Host` would be associated with the `Mapping` instead
+of requiring all labels in the selector to be present. Additonally, if the `hostname` of the `Mapping` matched the `hostname` of the `Host` then they would be associated
+regardless of the configuration of `mappingSelector`.
+In version `3.2` this bug was fixed and a `Host` will only be associated with a `Mapping` if **all** labels required by the selector are present.
+This brings the `mappingSelector` field in-line with how label selectors are used throughout Kubernetes. To avoid unexpected behavior after the upgrade,
+add all labels that `Hosts` have in their `mappingSelector` to `Mappings` you want to associate with the `Host`. You can opt-out of this fix and return to the old
+`Mapping`/`Host` association behavior by setting the environment variable `DISABLE_STRICT_LABEL_SELECTORS` to `"true"` (default: `"false"`). A future version of
+$productName$ may remove the ability to opt-out of this bugfix.
+
+## Secure and insecure requests
+
+A **secure** request arrives via HTTPS; an **insecure** request does not. By default, secure requests will be routed and insecure requests will be redirected (using an HTTP 301 response) to HTTPS. The behavior of insecure requests can be overridden using the `requestPolicy` element of a `Host`:
+
+```yaml
+requestPolicy:
+ insecure:
+ action: insecure-action
+ additionalPort: insecure-port
+```
+
+The `insecure-action` can be one of:
+
+- `Redirect` (the default): redirect to HTTPS
+- `Route`: go ahead and route as normal; this will allow handling HTTP requests normally
+- `Reject`: reject the request with a 400 response
+
+```yaml
+requestPolicy:
+ insecure:
+ additionalPort: -1 # This is how to disable the default redirection from 8080.
+```
+
+Some special cases to be aware of here:
+
+- **Case matters in the actions:** you must use e.g. `Reject`, not `reject`.
+- The `X-Forwarded-Proto` header is honored when determining whether a request is secure or insecure. For more information, see "Load Balancers, the `Host` Resource, and `X-Forwarded-Proto`" below.
+- ACME challenges with prefix `/.well-known/acme-challenge/` are always forced to be considered insecure, since they are not supposed to arrive over HTTPS.
+- $AESproductName$ provides native handling of ACME challenges. If you are using this support, $AESproductName$ will automatically arrange for insecure ACME challenges to be handled correctly. If you are handling ACME yourself - as you must when running $OSSproductName$ - you will need to supply appropriate `Host` resources and `Mapping`s to correctly direct ACME challenges to your ACME challenge handler.
+
+## TLS settings
+
+The `Host` is responsible for high-level TLS configuration in $productName$. There are
+several settings covering TLS:
+
+### ACME support
+
+$AESproductName$ comes with built in support for automatic certificate
+management using the [ACME protocol](https://tools.ietf.org/html/rfc8555).
+
+It does this by using the `hostname` of a `Host` to request a certificate from
+the `acmeProvider.authority` using the `HTTP-01` challenge. After requesting a
+certificate, $AESproductName$ will then manage the renewal process automatically.
+
+The `acmeProvider` element of the `Host` configures the Certificate Authority
+$AESproductName$ will request the certificate from and the email address that the CA
+will use to notify about any lifecycle events of the certificate.
+
+```yaml
+acmeProvider:
+ authority: url-to-provider
+ email: email-of-registrant
+```
+
+**Notes on ACME Support:**
+
+- If the authority is not supplied, the Let’s Encrypt production environment is assumed.
+
+- In general, `email-of-registrant` is mandatory when using ACME: it should be
+ a valid email address that will reach someone responsible for certificate
+ management.
+
+- ACME stores certificates in Kubernetes secrets. The name of the secret can be
+ set using the `tlsSecret` element:
+
+ ```yaml
+ acmeProvider:
+ email: user@example.com
+ tlsSecret:
+ name: tls-cert
+ ```
+
+ if not supplied, a name will be automatically generated from the `hostname` and `email`.
+
+- $AESproductName$ uses the [`HTTP-01` challenge
+ ](https://letsencrypt.org/docs/challenge-types/) for ACME support:
+ - Does not require permission to edit DNS records
+ - The `hostname` must be reachable from the internet so the CA can check
+ `POST` to an endpoint in $AESproductName$.
+ - Wildcard domains are not supported.
+
+### `tlsSecret` enables TLS termination
+
+`tlsSecret` specifies a Kubernetes `Secret` is **required** for any TLS termination to occur. If ACME is enabled,
+it will set `tlsSecret`: in all other cases, TLS termination will not occur if `tlsSecret` is not specified.
+
+The following `Host` will configure $productName$ to read a `Secret` named
+`tls-cert` for a certificate to use when terminating TLS.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ authority: none
+ tlsSecret:
+ name: tls-cert
+```
+
+### `tlsContext` links to a `TLSContext` for additional configuration
+
+`tlsContext` specifies a [`TLSContext`](#) to use for additional TLS information. Note that you **must** still
+define `tlsSecret` for TLS termination to happen. It is an error to supply both `tlsContext` and `tls`.
+
+See the [TLS discussion](../tls) for more details.
+
+### `tls` allows manually providing additional configuration
+
+`tls` allows specifying most of the things a `TLSContext` can, inline in the `Host`. Note that you **must** still
+define `tlsSecret` for TLS termination to happen. It is an error to supply both `tlsContext` and `tls`.
+
+See the [TLS discussion](../tls) for more details.
+
+## Load balancers, the `Host` resource, and `X-Forwarded-Proto`
+
+In a typical installation, $productName$ runs behind a load balancer. The
+configuration of the load balancer can affect how $productName$ sees requests
+arriving from the outside world, which can in turn can affect whether $productName$
+considers the request secure or insecure. As such:
+
+- **We recommend layer 4 load balancers** unless your workload includes
+ long-lived connections with multiple requests arriving over the same
+ connection. For example, a workload with many requests carried over a small
+ number of long-lived gRPC connections.
+- **$productName$ fully supports TLS termination at the load balancer** with a single exception, listed below.
+- If you are using a layer 7 load balancer, **it is critical that the system be configured correctly**:
+ - The load balancer must correctly handle `X-Forwarded-For` and `X-Forwarded-Proto`.
+ - The `l7Depth` element in the [`Listener` CRD](../../running/listener) must be set to the number of layer 7 load balancers the request passes through to reach $productName$ (in the typical case, where the client speaks to the load balancer, which then speaks to $productName$, you would set `l7Depth` to 1). If `l7Depth` remains at its default of 0, the system might route correctly, but upstream services will see the load balancer's IP address instead of the actual client's IP address.
+
+It's important to realize that Envoy manages the `X-Forwarded-Proto` header such that it **always** reflects the most trustworthy information Envoy has about whether the request arrived encrypted or unencrypted. If no `X-Forwarded-Proto` is received from downstream, **or if it is considered untrustworthy**, Envoy will supply an `X-Forwarded-Proto` that reflects the protocol used for the connection to Envoy itself. The `l7Depth` element is also used when determining trust for `X-Forwarded-For`, and it is therefore important to set it correctly. Its default of 0 should always be correct when $productName$ is behind only layer 4 load balancers; it should need to be changed **only** when layer 7 load balancers are involved.
+
+### CRD specification
+
+The `Host` CRD is formally described by its protobuf specification. Developers who need access to the specification can find it [here](https://github.com/emissary-ingress/emissary/blob/release/v2.0/api/getambassador.io/v2/Host.proto).
diff --git a/docs/edge-stack/3.9/topics/running/tls/cleartext-redirection.md b/docs/edge-stack/3.9/topics/running/tls/cleartext-redirection.md
new file mode 100644
index 000000000..74fc88eeb
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/tls/cleartext-redirection.md
@@ -0,0 +1,91 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Cleartext support
+
+While most modern web applications choose to encrypt all traffic, there remain
+cases where supporting cleartext communications is important. $productName$ supports
+both forcing [automatic redirection to HTTPS](#http-https-redirection) and
+[serving cleartext](#cleartext-routing) traffic on a `Host`.
+
+
+ If no Hosts are defined, $productName$ enables HTTP->HTTPS redirection. You will
+ need to explicitly create a Host to enable cleartext communication at all.
+
+
+
+ The Listener and
+ Host CRDs work together to manage HTTP and HTTPS routing.
+ This document is meant as a quick reference to the Host resource: for a more complete
+ treatment of handling cleartext and HTTPS, see Configuring $productName$ Communications.
+
+
+## Cleartext Routing
+
+To allow cleartext to be routed, set the `requestPolicy.insecure.action` of a `Host` to `Route`:
+
+```yaml
+requestPolicy:
+ insecure:
+ action: Redirect
+```
+
+This allows routing for either HTTP and HTTPS, or _only_ HTTP, depending on `tlsSecret` configuration:
+
+- If the `Host` does not specify a `tlsSecret`, it will only route HTTP, not terminating TLS at all.
+- If the `Host` does specify a `tlsSecret`, it will route both HTTP and HTTPS.
+
+
+ If no Hosts are defined, $productName$ enables HTTP->HTTPS redirection. You will
+ need to explicitly create a Host to enable cleartext communication at all.
+
+
+
+ The Listener and
+ Host CRDs work together to manage HTTP and HTTPS routing.
+ This document is meant as a quick reference to the Host resource: for a more complete
+ treatment of handling cleartext and HTTPS, see Configuring $productName$ Communications.
+
+
+## HTTP->HTTPS redirection
+
+Most websites that force HTTPS will also automatically redirect any
+requests that come into it over HTTP:
+
+```
+Client $productName$
+| |
+| http:///api |
+| --------------------------> |
+| |
+| 301: https:///api |
+| <-------------------------- |
+| |
+| https:///api |
+| --------------------------> |
+| |
+```
+
+In $productName$, this is configured by setting the `insecure.action` in a `Host` to `Redirect`.
+
+```yaml
+requestPolicy:
+ insecure:
+ action: Redirect
+```
+
+$productName$ determines which requests are secure and which are insecure using the
+`securityModel` of the [`Listener`] that accepts the request.
+
+[`Listener`]: ../../listener
+
+
+ If no Hosts are defined, $productName$ enables HTTP->HTTPS redirection. You will
+ need to explicitly create a Host to enable cleartext communication at all.
+
+
+
+ The Listener and
+ Host CRDs work together to manage HTTP and HTTPS routing.
+ This document is meant as a quick reference to the Host resource: for a more complete
+ treatment of handling cleartext and HTTPS, see Configuring $productName$ Communications.
+
diff --git a/docs/edge-stack/3.9/topics/running/tls/index.md b/docs/edge-stack/3.9/topics/running/tls/index.md
new file mode 100644
index 000000000..b60dcb9fd
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/running/tls/index.md
@@ -0,0 +1,520 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Transport Layer Security (TLS)
+
+$productName$'s robust TLS support exposes configuration options
+for different TLS use cases including:
+
+- [Simultaneously Routing HTTP and HTTPS](cleartext-redirection#cleartext-routing)
+- [HTTP -> HTTPS Redirection](cleartext-redirection#http-https-redirection)
+- [Mutual TLS](mtls)
+- [Server Name Indication (SNI)](sni)
+- [TLS Origination](origination)
+
+## Certificates and Secrets
+
+Properly-functioning TLS requires the use of [TLS certificates] to prove that the
+various systems communicating are who they say they are. At minimum, $productName$
+must have a server certificate that identifies it to clients; when [mTLS](./mtls)
+or [client certificate authentication] are in use, additional certificates are needed.
+
+You supply certificates to $productName$ in Kubernetes [TLS Secrets]. These Secrets
+_must_ contain valid X.509 certificates with valid PKCS1, PKCS8, or Elliptic Curve private
+keys. If a Secret does not contain a valid certificate, an error message will be logged, for
+example:
+
+```
+tls-broken-cert.default.1 2 errors:; 1. K8sSecret secret tls-broken-cert.default tls.key cannot be parsed as PKCS1 or PKCS8: asn1: syntax error: data truncated; 2. K8sSecret secret tls-broken-cert.default tls.crt cannot be parsed as x.509: x509: malformed certificate
+```
+
+If you set the `AMBASSADOR_FORCE_SECRET_VALIDATION` environment variable, the invalid
+Secret will be rejected, and a `Host` or `TLSContext` resource attempting to use an invalid
+certificate will be disabled entirely. **Note** that in $productName$ $version$, this
+includes disabling cleartext communication for such a `Host`.
+
+[tls certificates]: https://protonmail.com/blog/tls-ssl-certificate/
+[client certificate authentication]: ../../../howtos/client-cert-validation/
+[tls secrets]: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets
+
+## `Host`
+
+A `Host` represents a domain in $productName$ and defines how the domain manages TLS. For more information on the Host resource, see [The Host CRD reference documentation](../host-crd).
+
+**If no `Host`s are present**, $productName$ synthesizes a `Host` that
+terminates TLS using a self-signed TLS certificate, and redirects cleartext
+traffic to HTTPS. You will need to explictly define `Host`s to change this behavior
+(for example, to use a different certificate or to route cleartext).
+
+
+ The examples below do not define a requestPolicy; however, most real-world
+ usage of $productName$ will require defining the requestPolicy.
+
+ For more information, please refer to the Host documentation.
+
+
+### Automatic TLS with ACME
+
+With $AESproductName$, you can configure the `Host` to manage TLS by
+requesting a certificate from a Certificate Authority using the
+[ACME HTTP-01 challenge](https://letsencrypt.org/docs/challenge-types/).
+
+After you create a DNS record, configure $AESproductName$ to get a certificate from the default CA, [Let's Encrypt](https://letsencrypt.org), by providing a hostname and your email for the certificate:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ authority: https://acme-v02.api.letsencrypt.org/directory # Optional: The CA you want to get your certificate from. Defaults to Let's Encrypt
+ email: julian@example.com
+```
+
+$AESproductName$ will now request a certificate from the CA and store it in a Secret
+in the same namespace as the `Host`.
+
+**If you use ACME for multiple Hosts, add a wildcard Host too.**
+This is required to manage a known issue. This issue will be resolved in a future Ambassador Edge Stack release.
+
+### Bring your own certificate
+
+The `Host` can read a certificate from a Kubernetes Secret and use that certificate
+to terminate TLS on a domain.
+
+The following example shows the certificate contained in the Kubernetes Secret named
+`host-secret` configured to have $productName$ terminate TLS on the `host.example.com`
+domain:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ tlsSecret:
+ name: host-secret
+```
+
+By default, `tlsSecret` will only look for the named secret in the same namespace as the `Host`.
+In the above example, the secret `host-secret` will need to exist within the `default` namespace
+since that is the namespace of the `Host`.
+
+To reference a secret that is in a different namespace from the `Host`, the `namespace` field is required.
+The below example will configure the `Host` to use the `host-secret` secret from the `example` namespace.
+
+```yaml
+---
+apiVersion: getambassador.io/v2
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ authority: none
+ tlsSecret:
+ name: host-secret
+ namespace: example
+```
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+### Advanced TLS configuration with the `Host`
+
+You can specify TLS configuration directly in the `Host` via the `tls` field. This is the
+recommended method to do more advanced TLS configuration for a single `Host`.
+
+For example, the configuration to enforce a minimum TLS version on the `Host` looks as follows:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ tlsSecret:
+ name: min-secret
+ tls:
+ min_tls_version: v1.2
+```
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+The following fields are accepted in the `tls` field:
+
+```yaml
+tls:
+ cert_chain_file: # string
+ private_key_file: # string
+ ca_secret: # string
+ cacert_chain_file: # string
+ alpn_protocols: # string
+ cert_required: # bool
+ min_tls_version: # string
+ max_tls_version: # string
+ cipher_suites: # array of strings
+ ecdh_curves: # array of strings
+ sni: # string
+ crl_secret: # string
+```
+
+These fields have the same function as in the [`TLSContext`](#tlscontext) resource,
+as described below.
+
+### `Host` and `TLSContext`
+
+You can link a `Host` to a [`TLSContext`](#tlscontext) instead of defining `tls`
+settings in the `Host` itself. This is primarily useful for sharing settings between
+multiple `Host`s.
+
+#### Link a `TLSContext` to the `Host`
+
+
+ It is invalid to use both the tls setting and the tlsContext
+ setting on the same Host. The recommended setting is using the tls setting
+ unless you have multiple Hosts that need to share TLS configuration.
+
+
+To link a [`TLSContext`](#tlscontext) with a `Host`, create a [`TLSContext`](#tlscontext)
+with the desired configuration and link it to the `Host` by setting the `tlsContext.name`
+field in the `Host`. For example, to enforce a minimum TLS version on the `Host` above,
+create a `TLSContext` with any name with the following configuration:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: min-tls-context
+spec:
+ hosts:
+ - host.example.com
+ secret: min-secret
+ min_tls_version: v1.2
+```
+
+Next, link it to the `Host` via the `tlsContext` field as shown:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ tlsSecret:
+ name: min-secret
+ tlsContext:
+ name: min-tls-context
+```
+
+
+
+ The `Host` and the `TLSContext` must name the same Kubernetes Secret; if not,
+ $productName$ will disable TLS for the `Host`.
+
+
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+
+
+ The `Host`'s `hostname` and the `TLSContext`'s `hosts` must have compatible settings. If
+ they do not, requests may not be accepted.
+
+
+
+See [`TLSContext`](#tlscontext) below to read more on the description of these fields.
+
+#### Create a `TLSContext` with the name `{{AMBASSADORHOST}}-context` (DEPRECATED)
+
+
+ This implicit TLSContext linkage is deprecated and will be removed
+ in a future version of $productName$; it is not recommended for new
+ configurations. Any other TLS configuration in the Host will override
+ this implict TLSContext link.
+
+
+The `Host` will implicitly link to the `TLSContext` when a `TLSContext` exists with the following:
+
+- the name `{{NAME_OF_AMBASSADORHOST}}-context`
+- `hosts` in the `TLSContext` set to the same value as `hostname` in the `Host`, and
+- `secret` in the `TLSContext` set to the same value as `tlsSecret` in the `Host`
+
+**As noted above, this implicit linking is deprecated.**
+
+For example, another way to enforce a minimum TLS version on the `Host` above would
+be to simply create the `TLSContext` with the name `example-host-context` and then not modify the `Host`:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: example-host-context
+spec:
+ hosts:
+ - host.example.com
+ secret: host-secret
+ min_tls_version: v1.2
+```
+
+
+
+ The `Host` and the `TLSContext` must name the same Kubernetes Secret; if not,
+ $productName$ will disable TLS for the `Host`.
+
+
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+
+
+ The `Host`'s `hostname` and the `TLSContext`'s `hosts` must have compatible settings. If
+ they do not, requests may not be accepted.
+
+
+
+Full reference for all options available to the `TLSContext` can be found [below](#tlscontext).
+
+## TLSContext
+
+The `TLSContext` is used to configure advanced TLS options in $productName$.
+Remember, a `TLSContext` must always be paired with a `Host`.
+
+A full schema of the `TLSContext` can be found below with descriptions of the
+different configuration options.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: example-host-context
+spec:
+ # 'hosts' defines the hosts for which this TLSContext is relevant.
+ # It ties into SNI. A TLSContext without "hosts" is useful only for
+ # originating TLS.
+ # type: array of strings
+ #
+ # hosts: []
+
+ # 'sni' defines the SNI string to use on originated connections.
+ # type: string
+ #
+ # sni: None
+
+ # 'secret' defines a Kubernetes Secret that contains the TLS certificate we
+ # use for origination or termination. If not specified, $productName$ will look
+ # at the value of cert_chain_file and private_key_file.
+ # type: string
+ #
+ # secret: None
+
+ # 'ca_secret' defines a Kubernetes Secret that contains the TLS certificate we
+ # use for verifying incoming TLS client certificates.
+ # type: string
+ #
+ # ca_secret: None
+
+ # Tells $productName$ whether to interpret a "." in the secret name as a "." or
+ # a namespace identifier.
+ # type: boolean
+ #
+ # secret_namespacing: true
+
+ # 'cert_required' can be set to true to _require_ TLS client certificate
+ # authentication.
+ # type: boolean
+ #
+ # cert_required: false
+
+ # 'alpn_protocols' is used to enable the TLS ALPN protocol. It is required
+ # if you want to do GRPC over TLS; typically it will be set to "h2" for that
+ # case.
+ # type: string (comma-separated list)
+ #
+ # alpn_protocols: None
+
+ # 'min_tls_version' sets the minimum acceptable TLS version: v1.0, v1.1,
+ # v1.2, or v1.3. It defaults to v1.0.
+ # min_tls_version: v1.0
+
+ # 'max_tls_version' sets the maximum acceptable TLS version: v1.0, v1.1,
+ # v1.2, or v1.3. It defaults to v1.3.
+ # max_tls_version: v1.3
+
+ # Tells $productName$ to load TLS certificates from a file in its container.
+ # type: string
+ #
+ # cert_chain_file: None
+ # private_key_file: None
+ # cacert_chain_file: None
+```
+
+
+
+ `secret` and (if used) `ca_secret` must specify Kubernetes Secrets containing valid TLS
+ certificates. If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and either Secret contains
+ an invalid certificate, $productName$ will reject the Secret, which will also completely
+ disable any `Host` using the `TLSContext`; see [**Certificates and Secrets**](#certificates-and-secrets)
+ above.
+
+
+
+### ALPN protocols
+
+The `alpn_protocols` setting configures the TLS ALPN protocol. To use gRPC over
+TLS, set `alpn_protocols: h2`. If you need to support HTTP/2 upgrade from
+HTTP/1, set `alpn_protocols: h2,http/1.1` in the configuration.
+
+#### HTTP/2 support
+
+The `alpn_protocols` setting is also required for HTTP/2 support.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: tls
+spec:
+ secret: ambassador-certs
+ hosts: ['*']
+ alpn_protocols: h2[, http/1.1]
+```
+
+Without setting alpn_protocols as shown above, HTTP2 will not be available via
+negotiation and will have to be explicitly requested by the client.
+
+If you leave off http/1.1, only HTTP2 connections will be supported.
+
+### TLS parameters
+
+The `min_tls_version` setting configures the minimum TLS protocol version that
+$productName$ will use to establish a secure connection. When a client
+using a lower version attempts to connect to the server, the handshake will
+result in the following error: `tls: protocol version not supported`.
+
+The `max_tls_version` setting configures the maximum TLS protocol version that
+$productName$ will use to establish a secure connection. When a client
+using a higher version attempts to connect to the server, the handshake will
+result in the following error:
+`tls: server selected unsupported protocol version`.
+
+The `cipher_suites` setting configures the supported ciphers found below using the
+[configuration parameters for BoringSSL](https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#Cipher-suite-configuration) when negotiating a TLS 1.0-1.2 connection.
+This setting has no effect when negotiating a TLS 1.3 connection. When a client does not
+support a matching cipher a handshake error will result.
+
+The `ecdh_curves` setting configures the supported ECDH curves when negotiating
+a TLS connection. When a client does not support a matching ECDH a handshake
+error will result.
+
+```
+ - AES128-SHA
+ - AES256-SHA
+ - AES128-GCM-SHA256
+ - AES256-GCM-SHA384
+ - ECDHE-RSA-AES128-SHA
+ - ECDHE-RSA-AES256-SHA
+ - ECDHE-RSA-AES128-GCM-SHA256
+ - ECDHE-RSA-AES256-GCM-SHA384
+ - ECDHE-RSA-CHACHA20-POLY1305
+ - ECDHE-ECDSA-AES128-SHA
+ - ECDHE-ECDSA-AES256-SHA
+ - ECDHE-ECDSA-AES128-GCM-SHA256
+ - ECDHE-ECDSA-AES256-GCM-SHA384
+ - ECDHE-ECDSA-CHACHA20-POLY1305
+ - ECDHE-PSK-AES128-CBC-SHA
+ - ECDHE-PSK-AES256-CBC-SHA
+ - ECDHE-PSK-CHACHA20-POLY1305
+ - PSK-AES128-CBC-SHA
+ - PSK-AES256-CBC-SHA
+ - DES-CBC3-SHA
+```
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: tls
+spec:
+ hosts: ['*']
+ secret: ambassador-certs
+ min_tls_version: v1.0
+ max_tls_version: v1.3
+ cipher_suites:
+ - '[ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305]'
+ - '[ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305]'
+ ecdh_curves:
+ - X25519
+ - P-256
+```
+
+The `crl_secret` field allows you to reference a Kubernetes Secret that contains a certificate revocation list.
+If specified, $productName$ will verify that the presented peer certificate has not been revoked by this CRL even if they are otherwise valid. This provides a way to reject certificates before they expire or if they become compromised.
+The `crl_secret` field takes a PEM-formatted [Certificate Revocation List](https://en.wikipedia.org/wiki/Certificate_revocation_list) in a `crl.pem` entry.
+
+Note that if a CRL is provided for any certificate authority in a trust chain, a CRL must be provided for all certificate authorities in that chain. Failure to do so will result in verification failure for both revoked and unrevoked certificates from that chain.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: tls-crl
+spec:
+ hosts: ["*"]
+ secret: ambassador-certs
+ min_tls_version: v1.0
+ max_tls_version: v1.3
+ crl_secret: 'ambassador-crl'
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: ambassador-crl
+ namespace: ambassador
+type: Opaque
+data:
+ crl.pem: |
+ {BASE64 CRL CONTENTS}
+---
+```
diff --git a/docs/edge-stack/3.9/topics/using/dev-portal.md b/docs/edge-stack/3.9/topics/using/dev-portal.md
new file mode 100644
index 000000000..4b405b0a8
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/dev-portal.md
@@ -0,0 +1,425 @@
+> **Developer Portal API visualization is now available in Ambassador Cloud. These docs will remain as a historical reference for hosted Developer Portal installations. [Go to the quick start guide](/docs/cloud/latest/visualize-api/quick-start/).**
+
+# Developer Portal
+
+## Rendering API documentation
+
+The _Dev Portal_ uses the `Mapping` resource to automatically discover services known by
+the Ambassador Edge Stack.
+
+For each `Mapping`, the _Dev Portal_ will attempt to fetch an OpenAPI V3 document
+when a `docs` attribute is specified.
+
+### `docs` attribute in `Mapping`s
+
+This documentation endpoint is defined by the optional `docs` attribute in the `Mapping`.
+
+```yaml
+ docs:
+ path: "string" # optional; default is ""
+ url: "string" # optional; default is ""
+ ignored: bool # optional; default is false
+ display_name: "string" # optional; default is ""
+```
+
+where:
+
+* `path`: path for the OpenAPI V3 document.
+The Ambassador Edge Stack will append the value of `docs.path` to the `prefix`
+in the `Mapping` so it will be able to use Envoy's routing capabilities for
+fetching the documentation from the upstream service . You will need to update
+your microservice to return a Swagger or OAPI document at this URL.
+* `url`: absolute URL to an OpenAPI V3 document.
+* `ignored`: ignore this `Mapping` for documenting services. Note that the service
+will appear in the _Dev Portal_ anyway if another, non-ignored `Mapping` exists
+for the same service.
+* `display_name`: custom name to show for this service in the devportal.
+
+> Note:
+>
+> Previous versions of the _Dev Portal_ tried to obtain documentation automatically
+> from `/.ambassador-internal/openapi-docs` by default, while the current version
+> will not try to obtain documentation unless a `docs` attribute is specified.
+> Users should set `docs.path` to `/.ambassador-internal/openapi-docs` in their `Mapping`s
+> in order to keep the previous behavior.
+>
+>
+> The `docs` field of Mappings was not introduced until `Ambassador Edge Stack` version 1.9 because Ambassador was automatically searching for docs on `/.ambassador-internal/openapi-docs`
+> Make sure to update your CRDs with the following command if you are encountering problems after upgrading from an earlier version of Ambassador.
+```yaml
+ `kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml`
+```
+
+> If you are on an earlier version of Ambassador, either upgrade to a newer version, or make your documentation available on `/.ambassador-internal/openapi-docs`.
+
+Example:
+
+With the `Mapping`s below, the _Dev Portal_ would fetch OpenAPI documentation
+from `service-a:5000` at the path `/srv/openapi/` and from `httpbin` from an
+external URL. `service-b` would have no documentation.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-a
+spec:
+ prefix: /service-a/
+ rewrite: /srv/
+ service: service-a:5000
+ docs:
+ path: /openapi/ ## docs will be obtained from `/srv/openapi/`
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-b
+spec:
+ prefix: /service-b/
+ service: service-b ## no `docs` attribute, so service-b will not be documented
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: regular-httpbin
+spec:
+ hostname: "*"
+ host_rewrite: httpbin.org
+ prefix: /httpbin/
+ service: httpbin.org
+ docs:
+ url: https://httpbin.org/spec.json
+```
+
+> Notes on access to documentation `path`s:
+>
+> By default, all the `path`s where documentation has been found will **NOT** be publicly
+> exposed by the Ambassador Edge Stack. This is controlled by a special
+> `FilterPolicy` installed internally.
+
+> Limitations on Mappings with a `host` attribute
+>
+> The Dev Portal will ignore `Mapping`s that contain `host`s that cannot be
+> parsed as a valid hostname, or use a regular expression (when `host_regex: true`).
+
+### Publishing the documentation
+
+All rendered API documentation is published at the `/docs/` URL by default. Users can
+achieve a higher level of customization by creating a `DevPortal` resource.
+`DevPortal` resources allow the customization of:
+
+- _what_ documentation is published
+- _how_ it looks
+
+Users can create a `DevPortal` resource for specifying the default configuration for
+the _Dev Portal_, filtering `Mappings` and namespaces and specifying the content.
+
+> Note: when several `DevPortal` resources exist, the Dev Portal will pick a random
+> one and ignore the rest. A specific `DevPortal` can be used as the default configuration
+> by setting the `default` attribute to `true`. Future versions will
+> use other `DevPortals` for configuring alternative _views_ of the Dev Portal.
+
+`DevPortal` resources have the following syntax:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: "string"
+ namespace: "string"
+spec:
+ default: bool ## optional; default false
+ docs: ## optional; default is []
+ - service: "string" ## required
+ url: "string" ## required
+ content: ## optional
+ url: "string" ## optional; see below
+ branch: "string" ## optional; see below
+ dir: "string" ## optional; see below
+ selector: ## optional
+ matchNamespaces: ## optional; default is []
+ - "string"
+ matchLabels: ## optional; default is {}
+ "string": "string"
+ naming_scheme: "string" ## optional; supported values [ "namespace.name", "name.prefix" ]; default "namespace.name"
+ preserve_servers: bool ## optional; default false
+ search:
+ enabled: bool ## optional; default false
+ type: "string" ## optional; supported values ["title-only", "all-content"]; default "title-only"
+```
+
+where:
+
+* `default`: `true` when this is the default Dev Portal configuration.
+* `content`: see [section below](#styling-the-devportal).
+* `selector`: rules for filtering `Mapping`s:
+ * `matchNamespaces`: list of namespaces, used for filtering the `Mapping`s that
+ will be shown in the `DevPortal`. When multiple namespaces are provided, the `DevPortal`
+ will consider `Mapping`s in **any** of those namespaces.
+ * `matchLabels`: dictionary of labels, filtering the `Mapping`s that will
+ be shown in the `DevPortal`. When multiple labels are provided, the `DevPortal`
+ will only consider the `Mapping`s that match **all** the labels.
+* `docs`: static list of _service_/_documentation_ pairs that will be shown
+ in the _Dev Portal_. Only the documentation from this list will be shown in the _Dev Portal_
+ (unless additional docs are included with a `selector`).
+ * `service`: service name used for listing user-provided documentation.
+ * `url`: a full URL to a OpenAPI document for this service. This document will be
+ served _as it is_, with no extra processing from the _Dev Portal_ (besides replacing
+ the _hostname_).
+* `naming_scheme`: Configures how DevPortal docs are displayed and linked to in the UI.
+ * "namespace.name" will display the docs with the namespace and name of the mapping.
+ e.g. a Mapping named `quote` in namespace `default` will be displayed as `default.quote`
+ and its docs will have the relative path of `/default/quote`
+ * "name.prefix" will display the docs with the name and prefix of the mapping.
+ e.g. a Mapping named `quote` with a prefix `backend` will be displayed as `quote.backend`
+ and its docs will have the relative path of `/quote/backend`
+* `preserve_servers`: Configures the DevPortal to no longer dynamically build server definitions
+ for the "try it out" request builder by using the Edge Stack hostname. When set to `true`, the
+ DevPortal will instead display the server definitions from the `servers` section of the Open API
+ docs supplied to the DevPortal for the service.
+* `search`: as of Edge Stack 1.13.0, the DevPortal content is now searchable
+ * `enabled`: default `false``; set to true to enable search functionality.
+ * When `enabled=false`, the DevPortal search endpoint (`/[DEVPORTAL_PATH/api/search`) will return an empty response
+ * `type`: Configure the items fed into search
+ * `title-only` (default): only search over the names of DevPortal services and markdown pages
+ * `all-content`: Search over openapi spec content and markdown page content.
+
+Example:
+
+The scope of the default _Dev Portal_ can be restricted to
+`Mappings` with the `public-api: true` and `documented: true` labels by creating
+a `DevPortal` `ambassador` resource like this:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ content:
+ url: https://github.com/datawire/devportal-content-v2.git
+ selector:
+ matchLabels:
+ public-api: "true" ## labels for matching only some Mappings
+ documented: "true" ## (note that "true" must be quoted)
+```
+
+Example:
+
+The _Dev Portal_ can show a static list OpenAPI docs. In this example, a `eks.aws-demo`
+_service_ is shown with the documentation obtained from a URL. In addition,
+the _Dev Portal_ will show documentation for all the services discovered in the
+`aws-demo` namespace:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ docs:
+ - service: eks.aws-demo
+ url: https://api.swaggerhub.com/apis/kkrlogistics/amazon-elastic_kubernetes_service/2017-11-01/swagger.json
+ selector:
+ matchNamespaces:
+ - aws-demo ## matches all the services in the `aws-demo` namespace
+ ## (note that Mappings must contain a `docs` attribute)
+```
+
+> Note:
+>
+> The free and unlicensed versions of `Ambassador Edge Stack` only support documentation for five services in the `DevPortal`.
+> When you start publishing documentation for more services to your `DevPortal`, keep in mind that you will not see more than 5 OpenAPI documents even if you have more than 5 services properly configured to report their OpenAPI specifications.
+> For more information on extending the number of services in your `DevPortal` please contact sales via our [pricing information page](/editions/).
+
+
+## Styling the `DevPortal`
+
+The look and feel of a `DevPortal` can be fully customized for your particular
+organization by specifying a different `content`, customizing not only _what_
+is shown but _how_ it is shown, and giving the possibility to
+add some specific content on your API documentation (e.g., best practices,
+usage tips, etc.) depending on where it has been published.
+
+The default _Dev Portal_ content is loaded in order from:
+
+- the `ambassador` `DevPortal` resource.
+- the Git repo specified in the optional `DEVPORTAL_CONTENT_URL` environment variable.
+- the default repository at [GitHub](https://github.com/datawire/devportal-content-v2.git).
+
+To use your own styling, clone or copy the repository, create an `ambassador` `DevPortal`
+and update the `content` attribute to point to the repository. If you wish to use a
+private GitHub repository, create a [Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line)
+and include it in the `content` following the example below:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ content:
+ url: https://9cb034008ddfs819da268d9z13b7ecd26@github.com/datawire/private-devportal-repo.git
+ selector:
+ matchLabels:
+ public-api: true
+```
+
+The `content` can be have the following attributes:
+
+```yaml
+ content:
+ url: "string" ## optional; default is the default repo
+ branch: "string" ## optional; default is "master"
+ dir: "string" ## optional; default is "/"
+```
+
+where:
+
+* `url`: Git URL for the content
+* `branch`: the Git branch
+* `dir`: subdirectory in the Git repo
+
+#### Iterating on _Dev Portal_ styling and content
+
+**Local Development**
+
+Check out a local copy of your content repo and from within run the following docker image:
+
+```
+docker run -it --rm --volume $PWD:/content --entrypoint local-devportal --publish 1080:1080
+ docker.io/datawire/aes:$version$ /content
+```
+
+and open `http://localhost:1080` in your browser. Any changes made locally to
+devportal content will be reflected immediately on page refresh.
+
+> Note:
+>
+> The docker command above will only work for AES versions 1.13.0+.
+
+**Remote Ambassador**
+
+After committing and pushing changes to your devportal content repo changes to git, set your DevPortal to fetch from your branch:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ content:
+ url: $REPO_URL
+ branch: $DEVELOPMENT_BRANCH
+```
+
+Then you can force a reload of DevPortal content by hitting a refresh endpoint on your remote ambassador:
+
+```
+# first, get your ambassador service
+export AMBASSADOR_LB_ENDPOINT=$(kubectl -n ambassador get svc ambassador \
+ -o "go-template={{range .status.loadBalancer.ingress}}{{or .ip .hostname}}{{end}}")
+
+# Then refresh the DevPortal content
+curl -X POST -Lk ${AMBASSADOR_LB_ENDPOINT}/docs/api/refreshContent
+```
+
+> Note:
+>
+> The DevPortal does not share a cache between replicas, so the content refresh endpoint
+> will only refresh the content on a single replica. It is suggested that you use this
+> endpoint in a single replica Edge Stack setup.
+
+#### Customizing documentation names and paths
+
+The _Dev Portal_ displays the documentation's Mapping name and namespace by default,
+but you can override this behavior.
+
+To change the documentation naming scheme for the entire _Dev Portal_, you can set
+`naming_scheme` in the `DevPortal` resource:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ naming_scheme: "name.prefix"
+```
+
+With the above configuration, a mapping for `service-a`:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-a
+spec:
+ prefix: /path/
+ service: service-a:5000
+ docs:
+ path: /openapi/
+```
+
+Will be displayed in the _Dev Portal_ as `service-a.path`,
+and the API documentation will be accessed at `$AMBASSADOR_URL/docs/doc/service-a/path`.
+
+You can also override the display name of documentation on a per-mapping basis.
+Per-mapping overrides will take precedence over the `DevPortal` `naming_scheme`.
+
+A mapping for `service-b` with `display_name` set:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-b
+spec:
+ prefix: /otherpath/
+ service: service-b:5000
+ docs:
+ path: /openapi/
+ display_name: "Cat Service"
+```
+
+Will be displayed in the _Dev Portal_ as `Cat Service`, and the documentation will be
+accessed at `$AMBASSADOR_URL/docs/doc/Cat%20Service`.
+
+
+## Default configuration
+
+The _Dev Portal_ supports some default configuration in some environment variables
+(for backwards compatibility).
+
+### Environment variables
+
+The _Dev Portal_ can also obtain some default configuration from environment variables
+defined in the AES `Deployment`. This configuration method is considered deprecated and
+kept only for backwards compatibility: users should configure the default values with
+the `ambassador` `DevPortal`.
+
+| Setting | Description |
+| ------------------------ | ------------------------------------------------------------------------------ |
+| AMBASSADOR_URL | External URL of Ambassador Edge Stack; include the protocol (e.g., `https://`) |
+| POLL_EVERY_SECS | Interval for polling OpenAPI docs; default 60 seconds |
+| DEVPORTAL_CONTENT_URL | Default URL to the repository hosting the content for the Portal |
+| DEVPORTAL_CONTENT_DIR | Default content subdir (defaults to `/`) |
+| DEVPORTAL_CONTENT_BRANCH | Default content branch (defaults to `master`) |
+| DEVPORTAL_DOCS_BASE_PATH | Base path for api docs (defaults to `/doc/`) |
+
+## Visualize your API documentation in the cloud
+
+If you haven't already done so, you may want to [connect your cluster to Ambassador Cloud](../../../tutorials/getting-started). Connected clusters will automatically report your `Mapping`s' OpenAPI documents, allowing you to host and visualize all of your services API documentation on a shared, secure and authenticated platform.
diff --git a/docs/edge-stack/3.9/topics/using/filters/apikeys.md b/docs/edge-stack/3.9/topics/using/filters/apikeys.md
new file mode 100644
index 000000000..7cd9e3479
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/filters/apikeys.md
@@ -0,0 +1,125 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using The API Keys Filter
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied.
+
+
+
+See the [API Key Filter API reference][] for an overview of all the supported fields.
+
+## APIKey Filter Quickstart
+
+1. Come up with an API Key value to use. For this example, we're going to use the string `example-apikey-value`
+
+2. Convert the API Key value to [base64][].
+
+ You can do this however you prefer, such as with an online tool like [base64encode.org][] or with the terminal:
+
+ ```console
+ $ echo -n example-api-key-value | base64
+ ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+ ```
+
+3. Create an [APIKey Filter][] with the encoded API Key from above:
+
+ ```yaml
+ kubectl apply -f -<
+ If you want to create more APIKeys, you can continue to add them to your secret. The keys (key-1 in the example) used in the Secret do not matter, so you can name them whatever helps you keep track of the associated API Keys.
+
+
+4. Create a [FilterPolicy resource][] to use the `Filter` created above
+
+ ```yaml
+ kubectl apply -f -< GET /backend/ HTTP/1.1
+ > Accept: */*
+ >
+ < HTTP/1.1 403 Forbidden
+ < content-type: application/json
+ < server: envoy
+ <
+ {"message":"API key not found","requestId":"","statusCode":403}
+ ```
+
+
+ The request was denied because the header was not found, but it will also be denied if you send the correct header with an invalid API Key.
+
+
+6. Send a request with the APIKey header and value.
+
+ ```console
+ $ curl -ki http://$GATEWAY_HOST/backend/ -H "example-key-header: example-api-key-value"
+
+ > GET /backend/ HTTP/1.1
+ > Accept: */*
+ > example-key-header: example-api-key-value
+ >
+ < HTTP/1.1 200 OK
+ < content-type: application/json
+ < server: envoy
+ <
+ {
+ "server": "buoyant-raspberry-ju848o1i",
+ "quote": "A principal idea is omnipresent, much like candy.",
+ "time": "2023-08-04T03:40:45.594594388Z"
+ }
+ ```
+
+
+ Success! Your requests are now validated against an APIKey Filter and will be denied if they do not supply a valid API key!
+
+
+[API Key Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-apikey
+[APIKey Filter]: ../../../../custom-resources/getambassador/v3alpha1/filter-apikey
+[FilterPolicy resource]: ../../../../custom-resources/getambassador/v3alpha1/filterpolicy
+[base64encode.org]: https://www.base64encode.org/
+[base64]: https://en.wikipedia.org/wiki/Base64
diff --git a/docs/edge-stack/3.9/topics/using/filters/external.md b/docs/edge-stack/3.9/topics/using/filters/external.md
new file mode 100644
index 000000000..5e8e587e9
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/filters/external.md
@@ -0,0 +1,182 @@
+# Using The External Filter
+
+The `External` `Filter` calls out to an external service speaking the
+[`ext_authz` protocol](../../../running/services/ext-authz), providing
+a highly flexible interface to plug in your own authentication,
+authorization, and filtering logic.
+
+
+
+The `External` spec is identical to the [`AuthService`
+spec](../../../running/services/auth-service), with the following
+exceptions:
+
+* In an `AuthService`, the `tls` field must be a string referring to a
+ `TLSContext`. In an `External` `Filter`, it may only be a Boolean;
+ referring to a `TLSContext` is not supported.
+* In an `AuthService`, the default value of the `add_linkerd_headers`
+ field is based on the [`ambassador`
+ `Module`](../../../running/ambassador). In an `External` `Filter`,
+ the default value is always `false`.
+* `External` `Filters` lack the `stats_name`, and
+ `add_auth_headers` fields that `AuthServices` have.
+
+
+
+See the [External Filter API reference][] for an overview of all the supported fields.
+
+## Tracing Header Propagation
+
+If $productName$ is configured to use a `TraceService`, Envoy will send tracing information as gRPC Metadata. Add the trace headers to the `allowed_request_headers` field to propagate the trace headers when using an ExternalFilter configured with `proto:http`. For example, if using **Zipkin** with **B3 Propagation** headers you can configure your External Filter like this:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "my-ext-filter"
+ namespace: "my-namespace"
+spec:
+ External:
+ auth_service: "https://example-auth:3000"
+ proto: http
+ path_prefix: /check_request
+ allowed_request_headers:
+ - X-B3-Parentspanid
+ - X-B3-Sampled
+ - X-B3-Spanid
+ - X-B3-Traceid
+ - X-Envoy-Expected-Rq-Timeout-Ms
+ - X-Envoy-Internal
+ - X-Request-Id
+```
+
+## Configuring TLS Settings
+
+When an `External Filter` has the `auth_service` field configured with a URL that starts with `https://` then $productName$
+will attempt to communicate with the `AuthService` over a TLS connection. The following configurations are supported:
+
+1. Verify server certificate with host CA Certificates - *default when `tls: true`*
+2. Verify server certificate with provided CA Certificate
+3. Mutual TLS between client and server
+
+Overall, these new configuration options enhance the security of the communications between $productName$ and your `External Filter` by providing a way to
+verify the server's certificate, allowing customization of the trust verification process, and enabling mutual TLS (mTLS) between $productName$ and the
+`External Filter` service. By employing these security measures, users can have greater confidence in the authenticity, integrity,
+and confidentiality of their filter's actions, especially if it interacts with any sensitive information.
+
+### Example - Verify Server with Custom CA Certificate
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "my-ext-filter"
+ namespace: "my-namespace"
+spec:
+ External:
+ auth_service: "https://example-auth:3000"
+ proto: grpc
+ tlsConfig:
+ caCertificate:
+ fromSecret:
+ name: ca-cert-secret
+ namespace: shared-certs
+```
+
+### Example - Mutual TLS (mTLS)
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "my-ext-filter"
+ namespace: "my-namespace"
+spec:
+ External:
+ auth_service: "https://example-auth:3000"
+ proto: grpc
+ tlsConfig:
+ caCertificate:
+ fromSecret:
+ name: ca-cert-secret
+ namespace: shared-certs
+ certificate:
+ fromSecret:
+ name: client-cert-secret
+```
+
+## Metrics
+
+As of $productName$ 3.4.0, the following metrics for External Filters are available via the [metrics endpoint](../../../running/statistics/8877-metrics)
+
+| Metric | Type | Description |
+| ------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ambassador_edge_stack_external_filter_allowed` | Counter | Number of requests that were allowed by Ambassador Edge Stack External Filters. Includes requests that are allowed by failure_mode_allow when unable to connect to the External Filter. |
+| `ambassador_edge_stack_external_filter_denied` | Counter | Number of requests that were denied by Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter or due to a Filter config error. |
+| `ambassador_edge_stack_external_filter_error` | Counter | Number of errors returned directly from Ambassador Edge Stack External Filters and errors from an inability to connect to the External Filter |
+| `ambassador_edge_stack_external_handler_error` | Counter | Number of errors caused by Ambassador Edge Stack encountering invalid Filter config or an error while parsing the config. \nThese errors will always result in a HTTP 500 response being returned to the client and do not count towards metrics that track response codes from external filters. |
+| `ambassador_edge_stack_external_filter_rq_class` | Counter (with labels) | Aggregated counter of response code classes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter. |
+| `ambassador_edge_stack_external_filter_rq_status` | Counter (with labels) | Counter of response codes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter. |
+
+An example of what the metrics may look like can be seen below
+
+```
+# HELP ambassador_edge_stack_external_filter_allowed Number of requests that were allowed by Ambassador Edge Stack External Filters. Includes requests that are allowed by failure_mode_allow when unable to connect to the External Filter.
+# TYPE ambassador_edge_stack_external_filter_allowed counter
+ambassador_edge_stack_external_filter_allowed 2
+
+# HELP ambassador_edge_stack_external_filter_denied Number of requests that were denied by Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter or due to a Filter config error.
+# TYPE ambassador_edge_stack_external_filter_denied counter
+ambassador_edge_stack_external_filter_denied 12
+
+# HELP ambassador_edge_stack_external_filter_error Number of errors returned directly from Ambassador Edge Stack External Filters and errors from an inability to connect to the External Filter
+# TYPE ambassador_edge_stack_external_filter_error counter
+ambassador_edge_stack_external_filter_error 2
+
+# HELP ambassador_edge_stack_external_filter_rq_class Aggregated counter of response code classes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter.
+# TYPE ambassador_edge_stack_external_filter_rq_class counter
+ambassador_edge_stack_external_filter_rq_class{class="2xx"} 2
+ambassador_edge_stack_external_filter_rq_class{class="4xx"} 5
+ambassador_edge_stack_external_filter_rq_class{class="5xx"} 7
+
+# HELP ambassador_edge_stack_external_filter_rq_status Counter of response codes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter.
+# TYPE ambassador_edge_stack_external_filter_rq_status counter
+ambassador_edge_stack_external_filter_rq_status{status="200"} 2
+ambassador_edge_stack_external_filter_rq_status{status="401"} 3
+ambassador_edge_stack_external_filter_rq_status{status="403"} 2
+ambassador_edge_stack_external_filter_rq_status{status="500"} 2
+ambassador_edge_stack_external_filter_rq_status{status="501"} 5
+
+# HELP ambassador_edge_stack_external_handler_error Number of errors caused by Ambassador Edge Stack encountering invalid Filter config or an error while parsing the config. \nThese errors will always result in a HTTP 500 response being returned to the client and do not count towards metrics that track response codes from external filters.
+# TYPE ambassador_edge_stack_external_handler_error counter
+ambassador_edge_stack_external_handler_error 0
+```
+
+## Transport Protocol Migration
+
+> **Note:** The following information is only applicable to External Filters using `proto: grpc`
+As of $productName$ version 2.3, the `v2` transport protocol is deprecated and any External Filters making use
+of it should migrate to `v3` before support for `v2` is removed in a future release.
+
+The following imports simply need to be updated to migrate an External Filter
+
+`v2` Imports:
+
+```go
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+```
+
+`v3` Imports:
+
+```go
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+```
+
+In the [datawire/sample-external-service repository](https://github.com/datawire/Sample-External-Service), you can find examples of an External Filter using both the
+`v2` transport protocol as well as `v3` along with deployment instructions for reference. The External Filter in this repo does not perform any authorization and is instead meant to serve as a reference for the operations that an External can make use of.
+
+[External Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-external
diff --git a/docs/edge-stack/3.9/topics/using/filters/index.md b/docs/edge-stack/3.9/topics/using/filters/index.md
new file mode 100644
index 000000000..cefae8b4b
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/filters/index.md
@@ -0,0 +1,109 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using Filters and FilterPolicies
+
+Filters are used to extend the Ambassador Edge Stack to modify or intercept a request before sending to your
+backend service. The most common use case for Filters is authentication, and Edge Stack includes a number
+of built-in filters for this purpose. Edge Stack also supports developing custom filters.
+
+## Filter types
+
+Edge Stack supports the following filter types:
+
+- [JWT][]: Validates JSON Web Tokens
+- [OAuth2][]: Performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+- [Plugin][]: Allows users to write custom Filters in Go that run as part of the Edge Stack container
+- [External][]: Allows users to call out to other services for request processing. This can include both custom services (in any language) or third party services.
+- [API Keys][]: Validates API Keys present in a custom HTTP header
+
+## Managing Filters
+
+Filters are created with the `Filter` resource type, which contains global arguments to that filter.
+Which Filter(s) to use for which HTTP requests is then configured in [FilterPolicy resources][],
+which may contain path-specific arguments to the filter.
+
+## Using a Filter in a FilterPolicy
+
+In the example below, the `param-filter` Filter Plugin is loaded and configured to run on requests to `/httpbin/`.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: param-filter # This is the name used in FilterPolicy
+ namespace: standalone
+spec:
+ Plugin:
+ name: param-filter # The plugin's `.so` file's base name
+
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: httpbin-policy
+spec:
+ rules:
+ # Don't apply any filters to requests for /httpbin/ip
+ - host: "*"
+ path: /httpbin/ip
+ filters: null
+ # Apply param-filter and auth0 to requests for /httpbin/
+ - host: "*"
+ path: /httpbin/*
+ filters:
+ - name: param-filter
+ - name: auth0
+ # Default to authorizing all requests with auth0
+ - host: "*"
+ path: "*"
+ filters:
+ - name: auth0
+```
+
+
+ Edge Stack will choose the first FilterPolicy rule that matches the incoming request. As in the above example, you must list your rules in the order of least to most generic.
+
+
+## FilterPolicies With Multiple Domains
+
+In this example, the `foo-keycloak` filter is used for requests to `foo.bar.com`, while the `example-auth0` filter is used for requests to `example.com`. This configuration is useful if you are hosting multiple domains in the same cluster.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: multi-domain-policy
+spec:
+ rules:
+ - host: foo.bar.com
+ path: "*"
+ filters:
+ - name: foo-keycloak
+ - host: example.com
+ path: "*"
+ filters:
+ - name: example-auth0
+```
+
+## Filters Using Self-Signed Certificates
+
+The JWT and OAuth2 filters speak to other services over HTTP or HTTPS. If those services are configured to speak HTTPS using a self-signed certificate, attempting to talk to them will result in an error mentioning `ERR x509: certificate signed by unknown authority`. You can fix this by installing that self-signed certificate into the AES container by copying the certificate to `/usr/local/share/ca-certificates/` and then running `update-ca-certificates`. Note that the `aes` image sets `USER 1000` but `update-ca-certificates` needs to be run as root.
+
+The following Dockerfile will accomplish this procedure for you. When deploying Edge Stack, refer to that custom Docker image rather than to `docker.io/datawire/aes:$version$`
+
+```Dockerfile
+FROM docker.io/datawire/aes:$version$
+USER root
+COPY ./my-certificate.pem /usr/local/share/ca-certificates/my-certificate.crt
+RUN update-ca-certificates
+USER 1000
+```
+
+[JWT]: ./jwt
+[OAuth2]: ./oauth2
+[Plugin]: ./plugin
+[External]: ./external
+[API Keys]: ./apikeys
+[FilterPolicy resources]: ../../../custom-resources/getambassador/v3alpha1/filterpolicy
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
diff --git a/docs/edge-stack/3.9/topics/using/filters/jwt.md b/docs/edge-stack/3.9/topics/using/filters/jwt.md
new file mode 100644
index 000000000..bae8daa16
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/filters/jwt.md
@@ -0,0 +1,156 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using The JWT Filter
+
+The JWT filter type performs JWT validation on a [bearer token](https://tools.ietf.org/html/rfc6750) present in the HTTP header.
+If the bearer token JWT doesn't validate, or has insufficient scope, an RFC 6750-complaint error response with a `WWW-Authenticate`
+header is returned. The list of acceptable signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in
+`jwksURI`. Only RSA and `none` algorithms are supported.
+
+
+
+See the [JWT Filter API reference][] for an overview of all the supported fields.
+
+## JWT path-specific arguments
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: "example-jwt-filter"
+ arguments:
+ scope: # optional; default is []
+ - "scope-value-1"
+ - "scope-value-2"
+```
+
+`scope` is a list of OAuth scope values that Edge Stack will require to be listed in the [`scope` claim](https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-19#section-4.2). In addition to the normal values of the `scope` claim (a JSON string containing a space-separated list of values), the JWT Filter also accepts a JSON array of values.
+
+## Example configuration
+
+```yaml
+# Example results are for the JWT:
+#
+# eyJhbGciOiJub25lIiwidHlwIjoiSldUIiwiZXh0cmEiOiJzbyBtdWNoIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
+#
+# To save you some time decoding that JWT:
+#
+# header = {
+# "alg": "none",
+# "typ": "JWT",
+# "extra": "so much"
+# }
+# claims = {
+# "sub": "1234567890",
+# "name": "John Doe",
+# "iat": 1516239022
+# }
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: example-jwt-filter
+ namespace: example-namespace
+spec:
+ JWT:
+ jwksURI: "https://getambassador-demo.auth0.com/.well-known/jwks.json"
+ validAlgorithms:
+ - "none"
+ audience: "myapp"
+ requireAudience: false
+ injectRequestHeaders:
+ - name: "X-Fixed-String"
+ value: "Fixed String"
+ # result will be "Fixed String"
+ - name: "X-Token-String"
+ value: "{{ .token.Raw }}"
+ # result will be "eyJhbGciOiJub25lIiwidHlwIjoiSldUIiwiZXh0cmEiOiJzbyBtdWNoIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
+ - name: "X-Token-H-Alg"
+ value: "{{ .token.Header.alg }}"
+ # result will be "none"
+ - name: "X-Token-H-Typ"
+ value: "{{ .token.Header.typ }}"
+ # result will be "JWT"
+ - name: "X-Token-H-Extra"
+ value: "{{ .token.Header.extra }}"
+ # result will be "so much"
+ - name: "X-Token-C-Sub"
+ value: "{{ .token.Claims.sub }}"
+ # result will be "1234567890"
+ - name: "X-Token-C-Name"
+ value: "{{ .token.Claims.name }}"
+ # result will be "John Doe"
+ - name: "X-Token-C-Optional-Empty"
+ value: "{{ .token.Claims.optional }}"
+ # result will be ""; the header field will be set
+ # even if the "optional" claim is not set in the JWT.
+ - name: "X-Token-C-Optional-Unset"
+ value: "{{ if hasKey .token.Claims \"optional\" | not }}{{ doNotSet }}{{ end }}{{ .token.Claims.optional }}"
+ # Similar to "X-Token-C-Optional-Empty" above, but if the
+ # "optional" claim is not set in the JWT, then the header
+ # field won't be set either.
+ #
+ # Note that this does NOT remove/overwrite a client-supplied
+ # header of the same name. In order to distrust
+ # client-supplied headers, you MUST use a Lua script to
+ # remove the field before the Filter runs (see below).
+ - name: "X-Token-C-Iat"
+ value: "{{ .token.Claims.iat }}"
+ # result will be "1.516239022e+09" (don't expect JSON numbers
+ # to always be formatted the same as input; if you care about
+ # that, specify the formatting; see the next example)
+ - name: "X-Token-C-Iat-Decimal"
+ value: "{{ printf \"%.0f\" .token.Claims.iat }}"
+ # result will be "1516239022"
+ - name: "X-Token-S"
+ value: "{{ .token.Signature }}"
+ # result will be "" (since "alg: none" was used in this example JWT)
+ - name: "X-Authorization"
+ value: "Authenticated {{ .token.Header.typ }}; sub={{ .token.Claims.sub }}; name={{ printf \"%q\" .token.Claims.name }}"
+ # result will be: "Authenticated JWT; sub=1234567890; name="John Doe""
+ - name: "X-UA"
+ value: "{{ .httpRequestHeader.Get \"User-Agent\" }}"
+ # result will be: "curl/7.66.0" or
+ # "Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0"
+ # or whatever the requesting HTTP client is
+ errorResponse:
+ headers:
+ - name: "Content-Type"
+ value: "application/json"
+ - name: "X-Correlation-ID"
+ value: "{{ .httpRequestHeader.Get \"X-Correlation-ID\" }}"
+ # Regarding the "altErrorMessage" below:
+ # ValidationErrorExpired = 1<<4 = 16
+ # https://godoc.org/github.com/dgrijalva/jwt-go#StandardClaims
+ bodyTemplate: |-
+ {
+ "errorMessage": {{ .message | json " " }},
+ {{- if .error.ValidationError }}
+ "altErrorMessage": {{ if eq .error.ValidationError.Errors 16 }}"expired"{{ else }}"invalid"{{ end }},
+ "errorCode": {{ .error.ValidationError.Errors | json " "}},
+ {{- end }}
+ "httpStatus": "{{ .status_code }}",
+ "requestId": {{ .request_id | json " " }}
+ }
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ lua_scripts: |
+ function envoy_on_request(request_handle)
+ request_handle:headers():remove("x-token-c-optional-unset")
+ end
+```
+
+[JWT Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-jwt
diff --git a/docs/edge-stack/3.9/topics/using/filters/oauth2.md b/docs/edge-stack/3.9/topics/using/filters/oauth2.md
new file mode 100644
index 000000000..13aa7230c
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/filters/oauth2.md
@@ -0,0 +1,225 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using The OAuth2 Filter
+
+The OAuth2 filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html). The filter is both:
+
+* An OAuth Client, which fetches resources from the Resource Server on the user's behalf.
+* Half of a Resource Server, validating the Access Token before allowing the request through to the upstream service, which implements the other half of the Resource Server.
+
+This is different from most OAuth implementations where the Authorization Server and the Resource Server are in the same security domain. With Ambassador Edge Stack, the Client and the Resource Server are in the same security domain, and there is an independent Authorization Server.
+
+
+
+See the [OAuth2 Filter API reference][] for an overview of all the supported fields.
+
+## The Ambassador authentication flow
+
+This is what the authentication process looks like at a high level when using Ambassador Edge Stack with an external identity provider. The use case is an end-user accessing a secured app service.
+
+
+
+### Some basic authentication terms
+
+For those unfamiliar with authentication, here is a basic set of definitions.
+
+* OpenID: is an [open standard](https://openid.net/) and [decentralized authentication protocol](https://en.wikipedia.org/wiki/OpenID). OpenID allows users to be authenticated by co-operating sites, referred to as "relying parties" (RP) using a third-party authentication service. End-users can create accounts by selecting an OpenID identity provider (such as Auth0, Okta, etc), and then use those accounts to sign onto any website that accepts OpenID authentication.
+* Open Authorization (OAuth): an open standard for [token-based authentication and authorization](https://oauth.net/) on the Internet. OAuth provides to clients a "secure delegated access" to server or application resources on behalf of an owner, which means that although you won't manage a user's authentication credentials, you can specify what they can access within your application once they have been successfully authenticated. The current latest version of this standard is OAuth 2.0.
+* Identity Provider (IdP): an entity that [creates, maintains, and manages identity information](https://en.wikipedia.org/wiki/Identity_provider) for user accounts (also referred to "principals") while providing authentication services to external applications (referred to as "relying parties") within a distributed network, such as the web.
+* OpenID Connect (OIDC): is an [authentication layer that is built on top of OAuth 2.0](https://openid.net/connect/), which allows applications to verify the identity of an end-user based on the authentication performed by an IdP, using a well-specified RESTful HTTP API with JSON as a data format. Typically an OIDC implementation will allow you to obtain basic profile information for a user that successfully authenticates, which in turn can be used for implementing additional security measures like Role-based Access Control (RBAC).
+* JSON Web Token (JWT): is a [JSON-based open standard for creating access tokens](https://jwt.io/), such as those generated from an OAuth authentication. JWTs are compact, web-safe (or URL-safe), and are often used in the context of implementing single sign-on (SSO) within federated applications and organizations. Additional profile information, claims, or role-based information can be added to a JWT, and the token can be passed from the edge of an application right through the application's service call stack.
+
+If you look back at the authentication process diagram, the function of the entities involved should now be much clearer.
+
+### Using an identity hub
+
+Using an identity hub or broker allows you to support many IdPs without having to code individual integrations with them. For example, [Auth0](https://auth0.com/docs/identityproviders) and [Keycloak](https://www.keycloak.org/docs/latest/server_admin/index.html#social-identity-providers) both offer support for using Google and GitHub as an IdP.
+
+An identity hub sits between your application and the IdP that authenticates your users, which not only adds a level of abstraction so that your application (and Ambassador Edge Stack) is isolated from any changes to each provider's implementation, but it also allows your users to chose which provider they use to authenticate (and you can set a default, or restrict these options).
+
+The Auth0 docs provide a guide for adding social IdP "[connections](https://auth0.com/docs/identityproviders)" to your Auth0 account, and the Keycloak docs provide a guide for adding social identity "[brokers](https://www.keycloak.org/docs/latest/server_admin/index.html#social-identity-providers)".
+
+## OAuth2 path-specific arguments
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: "example-oauth2-filter"
+ arguments:
+ scope: # optional; default is ["openid"] for `grantType=="AuthorizationCode"`; [] for `grantType=="ClientCredentials"` and `grantType=="Password"`
+ - "scopevalue1"
+ - "scopevalue2"
+ scopes: # deprecated; use 'scope' instead
+ insteadOfRedirect: # optional for "AuthorizationCode"; default is to do a redirect to the identity provider
+ ifRequestHeader: # optional; default is to return httpStatusCode for all requests that would redirect-to-identity-provider
+ name: "string" # required
+ negate: bool # optional; default is false
+ # It is invalid to specify both "value" and "valueRegex".
+ value: "string" # optional; default is any non-empty string
+ valueRegex: "regex" # optional; default is any non-empty string
+ # option 1:
+ httpStatusCode: integer # optional; default is 403 (unless `filters` is set)
+ # option 2 (deprecated - will be removed in future version):
+ filters: # optional; default is to use `httpStatusCode` instead
+ - name: "string" # required
+ namespace: "string" # optional; default is the same namespace as the FilterPolicy
+ ifRequestHeader: # optional; default to apply this filter to all requests matching the host & path
+ name: "string" # required
+ negate: bool # optional; default is false
+ # It is invalid to specify both "value" and "valueRegex".
+ value: "string" # optional; default is any non-empty string
+ valueRegex: "regex" # optional; default is any non-empty string
+ onDeny: "enum" # optional; default is "break"
+ onAllow: "enum" # optional; default is "continue"
+ arguments: DEPENDS # optional
+ sameSite: "enum" # optional; the SameSite attribute to set on cookies created by this filter. valid values include: "lax", "strict", "none". by default, no SameSite attribute is set, which typically allows the browser to decide the value.
+```
+
+ - `scope`: A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope.
+
+ If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed in the `FilterPolicy` argument.
+
+ If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+
+ As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+
+ The ordering of scope values does not matter, and is ignored.
+
+ - `scopes` is deprecated, and is equivalent to setting `scope`.
+
+ - `insteadOfRedirect`: An action to perform instead of redirecting
+ the User-Agent to the identity provider, when using `grantType: "AuthorizationCode"`.
+ By default, if the User-Agent does not have a currently-authenticated session,
+ then the Ambassador Edge Stack will redirect the User-Agent to the
+ identity provider. Setting `insteadOfRedirect` allows you to modify
+ this behavior. `insteadOfRedirect` does nothing when `grantType:
+ "ClientCredentials"`, because the Ambassador Edge Stack will never
+ redirect the User-Agent to the identity provider for the client
+ credentials grant type.
+ * If `insteadOfRedirect` is non-`null`, then by default it will
+ apply to all requests that would cause the redirect; setting the
+ `ifRequestHeader` sub-argument causes it to only apply to
+ requests that have the HTTP header field
+ `name` (case-insensitive) either set to (if `negate: false`) or
+ not set to (if `negate: true`)
+ + a non-empty string if neither `value` nor `valueRegex` are set
+ + the exact string `value` (case-sensitive) (if `value` is set)
+ + a string that matches the regular expression `valueRegex` (if
+ `valueRegex` is set). This uses [RE2][] syntax (always, not
+ obeying `regex_type` in the `Module`) but does
+ not support the `\C` escape sequence.
+ * By default, it serves an authorization-denied error page; by default HTTP 403 ("Forbidden"), but this can be configured by the `httpStatusCode` sub-argument.
+ * __DEPRECATED__ Instead of serving that simple error page, it can instead be configured to call out to a list of other Filters, by setting the `filters` list. The syntax and semantics of this list are the same as `.spec.rules[].filters` in a `FilterPolicy`. Be aware that if one of these filters modify the request rather than returning a response, then the request will be allowed through to the backend service, even though the `OAuth2` Filter denied it.
+ * It is invalid to specify both `httpStatusCode` and `filters`.
+
+## XSRF protection
+
+The `ambassador_xsrf.NAME.NAMESPACE` cookie is an opaque string that should be used as an XSRF token. Applications wishing to leverage the Ambassador Edge Stack in their XSRF attack protection should take two extra steps:
+
+ 1. When generating an HTML form, the server should read the cookie, and include a `` element in the form.
+ 2. When handling submitted form data should verify that the form value and the cookie value match. If they do not match, it should refuse to handle the request, and return an HTTP 4XX response.
+
+Applications using request submission formats other than HTML forms should perform analogous steps of ensuring that the value is present in the request duplicated in the cookie and also in either the request body or secure header field. A secure header field is one that is not `Cookie`, is not "[simple](https://www.w3.org/TR/cors/#simple-header)", and is not explicitly allowed by the CORS policy.
+
+
+
+ Prior versions of Ambassador Edge Stack did not have an ambassador_xsrf.NAME.NAMESPACE cookie, and instead required you to use the ambassador_session.NAME.NAMESPACE cookie. The ambassador_session.NAME.NAMESPACE cookie should no longer be used for XSRF-protection purposes.
+
+
+## RP-initiated logout
+
+When a logout occurs, it is often not enough to delete the Ambassador
+Edge Stack's session cookie or session data; after this happens, and the web
+browser is redirected to the Identity Provider to re-log-in, the
+Identity Provider may remember the previous login, and immediately
+re-authorize the user; it would be like the logout never even
+happened.
+
+To solve this, the Ambassador Edge Stack can use [OpenID Connect Session
+Management](https://openid.net/specs/openid-connect-session-1_0.html)
+to perform an "RP-Initiated Logout", where Edge Stack
+(the OpenID Connect "Relying Party" or "RP")
+communicates directly with Identity Providers that support OpenID
+Connect Session Management, to properly log out the user.
+Unfortunately, many Identity Providers do not support OpenID Connect
+Session Management.
+
+This is done by having your application direct the web browser `POST`
+*and navigate* to `/.ambassador/oauth2/logout`. There are 2
+form-encoded values that you need to include:
+
+ 1. `realm`: The `name.namespace` of the `Filter` that you want to log
+ out of. This may be submitted as part of the POST body, or may be set as a URL query parameter.
+ 2. `_xsrf`: The value of the `ambassador_xsrf.{{realm}}` cookie
+ (where `{{realm}}` is as described above). This must be set in the POST body, the URL query part will not be checked.
+
+### Example configurations
+
+```html
+
+```
+
+```html
+
+```
+
+Using JavaScript:
+
+```js
+function getCookie(name) {
+ var prefix = name + "=";
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = cookies[i].trimStart();
+ if (cookie.indexOf(prefix) == 0) {
+ return cookie.slice(prefix.length);
+ }
+ }
+ return "";
+}
+
+function logout(realm) {
+ var form = document.createElement('form');
+ form.method = 'post';
+ form.action = '/.ambassador/oauth2/logout?realm='+realm;
+ //form.target = '_blank'; // uncomment to open the identity provider's page in a new tab
+
+ var xsrfInput = document.createElement('input');
+ xsrfInput.type = 'hidden';
+ xsrfInput.name = '_xsrf';
+ xsrfInput.value = getCookie("ambassador_xsrf."+realm);
+ form.appendChild(xsrfInput);
+
+ document.body.appendChild(form);
+ form.submit();
+}
+```
+
+## Redis
+
+The Ambassador Edge Stack relies on Redis to store short-lived authentication credentials and rate limiting information. If the Redis data store is lost, users will need to log back in and all existing rate-limits would be reset.
+
+## Further reading
+
+In this architecture, Ambassador Edge Stack is functioning as an Identity Aware Proxy in a Zero Trust Network. For more about this security architecture, read the [BeyondCorp security architecture whitepaper](https://ai.google/research/pubs/pub43231) by Google.
+
+The ["How-to" section](../../../../howtos/) has detailed tutorials on integrating Ambassador with a number of Identity Providers.
+
+[OAuth2 Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-oauth2
+[RE2]: https://github.com/google/re2/wiki/Syntax
diff --git a/docs/edge-stack/3.9/topics/using/filters/plugin.md b/docs/edge-stack/3.9/topics/using/filters/plugin.md
new file mode 100644
index 000000000..9c5781210
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/filters/plugin.md
@@ -0,0 +1,33 @@
+# Developing Plugin Filters
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled to a `.so` file, which you load into the Edge Stack container at `/etc/ambassador-plugins/${NAME}.so`. The [Filter Development Guide](../../../../howtos/filter-dev-guide) contains a tutorial on developing filters.
+
+
+
+See the [Plugin Filter API reference][] for an overview of all the supported fields.
+
+## The Plugin interface
+
+This code is written in the Go programming language and must be compiled with the exact same compiler settings as Edge Stack (any overlapping libraries used must have their versions match exactly). This information is documented in the `/ambassador/aes-abi.txt` file in the AES docker image.
+
+Plugins are compiled with `go build -buildmode=plugin -trimpath` and must have a `main.PluginMain` function with the signature `PluginMain(w http.ResponseWriter, r *http.Request)`:
+
+```go
+package main
+
+import (
+ "net/http"
+)
+
+func PluginMain(w http.ResponseWriter, r *http.Request) { … }
+```
+
+`*http.Request` is the incoming HTTP request that can be mutated or intercepted, which is done by `http.ResponseWriter`.
+
+Headers can be mutated by calling `w.Header().Set(HEADERNAME, VALUE)`.
+Finalize changes by calling `w.WriteHeader(http.StatusOK)`.
+
+If you call `w.WriteHeader()` with any value other than 200 (`http.StatusOK`) instead of modifying the request, the plugin has
+taken over the request, and the request will not be sent to your backend service. You can call `w.Write()` to write the body of an error page.
+
+[Plugin Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-plugin
diff --git a/docs/edge-stack/3.9/topics/using/licenses.md b/docs/edge-stack/3.9/topics/using/licenses.md
new file mode 100644
index 000000000..648f4deaf
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/licenses.md
@@ -0,0 +1,54 @@
+# $productName$ Licenses
+
+$productName$ requires a valid Enterprise license or Community license to start up. The Community license allows you to use $productName$ for free with certain restrictions and the Enterprise license lifts these restrictions for further use of premium features.
+
+For more details on the different licenses, please visit the [editions page](/editions).
+
+## Enterprise License
+To obtain an Enterprise license, you can [reach out to our sales team][] for more information.
+
+If you have any questions regarding your Enterprise license, or require an air gapped license, please to reach out to [support][].
+
+## Applying a License
+The process for applying a license is the same, regardless of which plan you choose:
+
+* Enterprise License: If you have already purchased an Enterprise plan, you can follow the steps below to connect your clusters to Ambassador Cloud. Your Enterprise license will automatically apply to all clusters that you connect. If you believe you have an Enterprise license, but this is not reflected in Ambassador Cloud after connecting your clusters, please reach out to [support][].
+
+* Community License: If you wish to utilize a free Community license for your Edge Stack clusters, you can follow the steps below to connect your clusters to Ambassador Cloud, and the Community license will be automatically applied.
+
+
+1. Installing the cloud connect token
+
+ You can follow the instructions on [the quickstart guide][] to get signed into [Ambassador Cloud][] and obtain a cloud connect token for your installation of $productName$ if you don't already have one.
+ This will let $productName$ request and renew your license from Ambassador Cloud.
+
+ The Cloud Connect Token is a `ConfigMap` that you will install in your Kubernetes cluster and looks like this:
+
+ ```yaml
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: edge-stack-agent-cloud-token
+ namespace: ambassador
+ data:
+ CLOUD_CONNECT_TOKEN:
+ ```
+
+2. Install the Cloud Connect Token
+
+ If you are using Helm, you can use Helm to manage your installation.
+
+ ```bash
+ helm install edge-stack --namespace ambassador datawire/edge-stack --set emissary-ingress.createDefaultListeners=true --set emissary-ingress.agent.cloudConnectToken=
+ ```
+
+ If you do not want to use Helm, then you can apply the Cloud Connect Token with raw yaml instead.
+
+ ```bash
+ kubectl create configmap --namespace ambassador edge-stack-agent-cloud-token --from-literal=CLOUD_CONNECT_TOKEN=
+ ```
+
+[reach out to our sales team]: /contact-us/
+[the quickstart guide]: ../../../tutorials/getting-started
+[Ambassador Cloud]: https://app.getambassador.io/cloud/
+[support]: https://support.datawire.io
diff --git a/docs/edge-stack/3.9/topics/using/rate-limits/index.md b/docs/edge-stack/3.9/topics/using/rate-limits/index.md
new file mode 100644
index 000000000..b6e90faa7
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/rate-limits/index.md
@@ -0,0 +1,193 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Basic rate limiting
+
+Rate limiting in $productName$ is composed of two parts:
+
+* The [`RateLimitService`] resource tells $productName$ what external service
+ to use for rate limiting.
+
+ If $productName$ cannot contact the rate limit service, it will allow the request to be processed as if there were no rate limit service configuration.
+
+* _Labels_ that get attached to requests. A label is basic metadata that
+ is used by the `RateLimitService` to decide which limits to apply to
+ the request.
+
+
+ These labels require Mapping resources with apiVersion
+ getambassador.io/v2 or newer — if you're updating an old installation, check the
+ apiVersion!
+
+
+Labels are grouped according to _domain_ and _group_:
+
+```yaml
+labels:
+ "domain1":
+ - "group1":
+ - "my_label_specifier_1"
+ - "my_label_specifier_2"
+ - "group2":
+ - "my_label_specifier_3"
+ - "my_label_specifier_4"
+ "domain2":
+ - ...
+```
+
+## Attaching labels to requests
+
+There are two ways of setting labels on a request:
+
+1. You can set labels on an individual [`Mapping`](../mappings). These labels
+ will only apply to requests that use that `Mapping`.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: foo-mapping
+ spec:
+ hostname: "*"
+ prefix: /foo/
+ service: foo
+ labels:
+ "domain1":
+ - "group1":
+ - "my_label_specifier_1"
+ - "my_label_specifier_2"
+ - "group2":
+ - "my_label_specifier_3"
+ - "my_label_specifier_4"
+ "domain2":
+ - ...
+ ```
+
+2. You can set global labels in the [`ambassador` `Module`](../../running/ambassador).
+ These labels will apply to _every_ request that goes through $productName$.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Module
+ metadata:
+ name: ambassador
+ spec:
+ config:
+ default_labels:
+ "domain1":
+ defaults:
+ - "my_label_specifier_a"
+ - "my_label_specifier_b"
+ "domain2":
+ defaults:
+ - "my_label_specifier_c"
+ - "my_label_specifier_d"
+ ```
+
+ If a `Mapping` and the defaults both give label groups for the same domain, the
+ default labels are prepended to each label group from the `Mapping`. If the `Mapping`
+ does not give any labels for that domain, the global labels are placed into a new
+ label group named "default" for that domain.
+
+Each label group is a list of labels; each label is a key/value pair. Since the label
+group is a list rather than a map:
+- it is possible to have multiple labels with the same key, and
+- the order of labels matters.
+
+> Note: The terminology used by the Envoy documentation differs from
+> the terminology used by $productName$:
+>
+> | $productName$ | Envoy |
+> |-----------------|-------------------|
+> | label group | descriptor |
+> | label | descriptor entry |
+> | label specifier | rate limit action |
+
+The `Mapping`s' listing of the groups of specifiers have names for the
+groups; the group names are useful for humans dealing with the YAML,
+but are ignored by $productName$, all $productName$ cares about are the
+*contents* of the groupings of label specifiers.
+
+There are 5 types of label specifiers in $productName$:
+
+
+
+1. `source_cluster`
+
+ ```yaml
+ source_cluster:
+ key: source_cluster
+ ```
+
+ Sets the label `source_cluster=«Envoy source cluster name»"`. The Envoy
+ source cluster name is the name of the Envoy cluster that the request came
+ in on.
+
+ The syntax of this label currently _requires_ `source_cluster: {}`.
+
+2. `destination_cluster`
+
+ ```yaml
+ destination_cluster:
+ key: destination_cluster
+ ```
+
+ Sets the label `destination_cluster=«Envoy destination cluster name»"`. The Envoy
+ destination cluster name is the name of the Envoy cluster to which the `Mapping`
+ routes the request. You can get the name for a cluster from the
+ [diagnostics service](../../running/diagnostics/).
+
+ The syntax of this label currently _requires_ `destination_cluster: {}`.
+
+3. `remote_address`
+
+ ```yaml
+ remote_address:
+ key: remote_address
+ ```
+
+ Sets the label `remote_address=«IP address of the client»"`. The IP address of
+ the client will be taken from the `X-Forwarded-For` header, to correctly manage
+ situations with L7 proxies. This requires that $productName$ be correctly
+ [configured to communicate](../../../howtos/configure-communications).
+
+ The syntax of this label currently _requires_ `remote_address: {}`.
+
+4. `request_headers`
+
+ ```yaml
+ request_headers:
+ header_name: "header-name"
+ key: mykey
+ ```
+
+ If a header named `header-name` is present, set the label `mykey=«value of the header»`.
+ If no header named `header-name` is present, **the entire label group is dropped**.
+
+5. `generic_key`
+
+ ```yaml
+ generic_key:
+ key: mykey
+ value: myvalue
+ ```
+
+ Sets the label `«mykey»=«myval»`. Note that supplying a `key` is supported only
+ with the Envoy V3 API.
+
+## Rate limiting requests based on their labels
+
+This is determined by your `RateLimitService` implementation.
+
+$AESproductName$ provides a `RateLimitService` implementation that is
+configured by a `RateLimit` custom resource.
+
+See the [$AESproductName$ RateLimit Reference](./rate-limits) for information on how
+to configure `RateLimit`s in $AESproductName$.
+
+See the [Basic Rate Limiting tutorial](../../../howtos/rate-limiting-tutorial) for an
+example `RateLimitService` implementation for $OSSproductName$.
diff --git a/docs/edge-stack/3.9/topics/using/rate-limits/rate-limits.md b/docs/edge-stack/3.9/topics/using/rate-limits/rate-limits.md
new file mode 100644
index 000000000..f764000bc
--- /dev/null
+++ b/docs/edge-stack/3.9/topics/using/rate-limits/rate-limits.md
@@ -0,0 +1,534 @@
+# Rate limiting reference
+
+Rate limiting in $productName$ is composed of two parts:
+
+* Labels that get attached to requests; a label is basic metadata that
+ is used by the `RateLimitService` to decide which limits to apply to
+ the request.
+* `RateLimit`s configure $productName$'s built-in
+ `RateLimitService`, and set limits based on the labels on the
+ request.
+
+
+> This page covers using `RateLimit` resources to configure $productName$
+ to rate limit requests. See the [Basic Rate Limiting article](../) for
+ information on adding labels to requests.
+
+
+## Rate limiting requests based on their labels
+
+A `RateLimit` resource defines a list of limits that apply to
+different requests.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: example-limits
+spec:
+ domain: "my_domain"
+ limits:
+ - name: per-minute-limit # optional; default is the `$name.$namespace-$idx` where name is the name of the CRD and idx is the index into the limits array
+ action: Enforce # optional; default to "Enforce". valid values are "Enforce" and "LogOnly", case insensitive.
+ pattern:
+ - "my_key1": "my_value1"
+ "my_key2": "my_value2"
+ - "my_key3": "my_value3"
+ rate: 5
+ unit: "minute"
+ injectRequestHeaders: # optional
+ - name: "header-name-string-1" # required
+ value: "go-template-string" # required
+ - name: "header-name-string-2" # required
+ value: "go-template-string" # required
+ injectResponseHeaders: # optional
+ - name: "header-name-string-1" # required
+ value: "go-template-string" # required
+ errorResponse: # optional
+ headers: # optional; default is [], adding no additional headers
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ bodyTemplate: "string" # optional; default is "", returning no response body
+ - name: per-second-limit
+ action: Enforce
+ pattern:
+ - "my_key4": "" # check the key but not the value
+ - "my_key5": "*" # check the key but not the value
+ rate: 5
+ unit: "second"
+ ...
+```
+
+It makes no difference whether limits are defined together in one
+`RateLimit` resource or are defined separately in many `RateLimit`
+resources.
+
+
+
+ - `name`: The symbolic name for this ratelimit. Used to set dynamic metadata that can be referenced in the Envoy access log.
+
+ - `action`: Each limit has an *action* that it will take when it is exceeded. Actions include:
+
+ * `Enforce` - enforce this limit on the client by returning HTTP 429. This is the default action.
+ * `LogOnly` - do not enforce this limit on the client, and allow the client request upstream if no other limit applies.
+
+ - `pattern`: Each limit has a *pattern* that matches against a label
+ group on a request to decide if that limit should apply to that
+ request. For a pattern to match, the request's label group must
+ start with exactly the labels specified in the pattern, in order.
+ If a label in a pattern has an empty string or `"*"` as the value,
+ then it only checks the key of that label on the request; not the
+ value. If a list item in the pattern has multiple key/value pairs,
+ if any of them match the label then it is considered a match.
+
+ For example, the pattern
+
+ ```yaml
+ pattern:
+ - "key1": "foo"
+ "key1": "bar"
+ - "key2": ""
+ ```
+
+ matches the label group
+
+ ```yaml
+ - key1: foo
+ - key2: baz
+ - otherkey: knob
+ ```
+
+ and
+
+ ```yaml
+ - key1: bar
+ - key2: baz
+ - otherkey: knob
+ ```
+
+ but not the label group
+
+ ```yaml
+ - key0: frob
+ - key1: foo
+ - key2: baz
+ ```
+
+ If a label group is matched by multiple patterns, the pattern with
+ the longest list of items wins.
+
+ If a request has multiple label groups, then multiple limits may apply
+ to that request; if *any* of the limits are being hit, then Ambassador
+ will reject the request as an HTTP 429.
+
+ - `rate`, `unit`: The limit itself is specified as an integer number
+ of requests per a unit of time. Valid units of time are `second`,
+ `minute`, `hour`, or `day` (all case-insensitive).
+
+ So for example
+
+ ```yaml
+ rate: 5
+ unit: minute
+ ```
+
+ would allow 5 requests per minute, and any requests in excess of
+ that would result in HTTP 429 errors. Note that the limit is
+ tracked in terms of wall clock minutes and not a sliding
+ window. For example if 5 requests happen 59 seconds into the
+ current wall clock minute, then clients only need to wait a second
+ in order to make another 5 requests.
+
+ - `burstFactor`: The optional `burstFactor` field changes enforcement
+ of ratelimits in two ways:
+
+ * A `burstFactor` of `N` will allow unused requests from a window
+ of `N` time units to be rolled over and included in the current
+ request limit. This will effectively result in two separate
+ ratelimits being applied depending on the dynamic behavior of
+ clients. Clients that only make occasional bursts will end up
+ with an effective ratelimit of `burstFactor` * `rate`, whereas
+ clients that make requests continually will be limited to just
+ `rate`. For example:
+
+ ```yaml
+ rate: 5
+ unit: minute
+ burstFactor: 5
+ ```
+
+ would allow bursts of up to 25 request per minute, but only
+ permit continual usage of 5 requests per minute.
+
+ * A `burstFactor` of `1` is logically very similar to no
+ `burstFactor` with one key difference. When `burstFactor` is
+ specified, requests are tracked with a sliding window rather than
+ in terms of wall clock minutes. For example:
+
+ ```yaml
+ rate: 5
+ unit: minute
+ burstFactor: 1
+ ```
+
+ With*out* the `burstFactor` of 1, the above limit would permit up
+ to 5 requests within any wall clock minute. *With* the
+ `burstFactor` of 1 it means that no more than 5 requests are
+ permitted within any 1 minute sliding window.
+
+ Note that the `burstFactor` field only works when the
+ `AES_RATELIMIT_PREVIEW` environment variable is set to `true`.
+
+ - `injectRequestHeaders`, `injectResponseHeaders`: If this limit's
+ pattern matches the request, then `injectRequestHeaders` injects
+ HTTP header fields in to the request before sending it to the
+ upstream service (assuming the limit even allows the request to go
+ to the upstream service), and `injectResponseHeaders` injects
+ headers in to the response sent back to the client (whether the
+ response came from the upstream service or is an HTTP 429 response
+ because it got rate limited). This is very similar to
+ `injectRequestHeaders` in a [`JWT` Filter][]. The header value is
+ specified as a [Go `text/template`][] string, with the following
+ data made available to it:
+
+ * `.RateLimitResponse.OverallCode` → `int` : `1` for OK, `2` for
+ OVER_LIMIT.
+ * `.RateLimitResponse.Statuses` →
+ [`[]*RateLimitResponse_DescriptorStatus]`]`v2.RateLimitResponse_DescriptorStatus`
+ The itemized status codes for each limit that was selected for
+ this request.
+ * `.RetryAfter` → `time.Duration` the amount of time until all of
+ the limits would allow access again (0 if they all currently
+ allow access).
+
+ Also available to the template are the [standard functions available
+ to Go `text/template`s][Go `text/template` functions], as well as:
+
+ * a `hasKey` function that takes the a string-indexed map as arg1,
+ and returns whether it contains the key arg2. (This is the same
+ as the [Sprig function of the same name][Sprig `hasKey`].)
+
+ * a `doNotSet` function that causes the result of the template to
+ be discarded, and the header field to not be adjusted. This is
+ useful for only conditionally setting a header field; rather
+ than setting it to an empty string or `""`. Note that
+ this does _not_ unset an existing header field of the same name.
+
+ - `errorResponse` allows templating the error response, overriding the default json error format. Make sure you validate and test your template, not to generate server-side errors on top of client errors.
+ * `headers` sets extra HTTP header fields in the error response. The value is specified as a [Go `text/template`][] string, with the same data made available to it as `bodyTemplate` (below). It does not have access to the `json` function.
+ * `bodyTemplate` specifies body of the error; specified as a [Go `text/template`][] string, with the following data made available to it:
+
+ * `.status_code` → `integer` the HTTP status code to be returned
+ * `.message` → `string` the error message string
+ * `.request_id` → `string` the Envoy request ID, for correlation (hidden from `{{ . | json "" }}` unless `.status_code` is in the 5XX range)
+ * `.RateLimitResponse.OverallCode` → `int` : `1` for OK, `2` for
+ OVER_LIMIT.
+ * `.RateLimitResponse.Statuses` →
+ [`[]*RateLimitResponse_DescriptorStatus]`]`v3.RateLimitResponse_DescriptorStatus`
+ The itemized status codes for each limit that was selected for
+ this request.
+ * `.RetryAfter` → `time.Duration` the amount of time until all of
+ the limits would allow access again (0 if they all currently
+ allow access).
+
+ Also availabe to the template are the [standard functions
+ available to Go `text/template`s][Go `text/template` functions],
+ as well as:
+
+ * a `json` function that formats arg2 as JSON, using the arg1
+ string as the starting indentation. For example, the
+ template `{{ json "indent>" "value" }}` would yield the
+ string `indent>"value"`.
+
+[`JWT` Filter]: ../../filters/jwt
+[Go `text/template`]: https://golang.org/pkg/text/template/
+[Go `text/template` functions]: https://golang.org/pkg/text/template/#hdr-Functions
+[Sprig `hasKey`]: https://masterminds.github.io/sprig/dicts.html#haskey
+
+## Logging RateLimits
+
+It is often desirable to know which RateLimit, if any, is applied to a client's request. This can be achieved by leveraging dynamic metadata available to Envoy's access log.
+
+The following dynamic metadata keys are available under the `envoy.filters.http.ratelimit` namespace. See https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage for more on Envoy's access log format.
+
+* `aes.ratelimit.name` - The symbolic `name` of the `Limit` on a `RateLimit` object that triggered the ratelimit action.
+* `aes.ratelimit.action` - The action that the `Limit` took. Possible values include `Enforce` and `LogOnly`. When the action is `Enforce`, the client was ratelimited with HTTP 429. When the action is `LogOnly`, the ratelimit was not enforced and the client's request was allowed upstream.
+* `aes.ratelimit.retry_after` - The time in seconds until the `Limit` resets. Equivalent to the value of the `Retry-After` returned to the client if the limit was enforced.
+
+If a `Limit` with a `LogOnly` action is exceeded and there are no other non-`LogOnly` `Limit`s that were exceeded, the request will be allowed upstream and that `Limit` will available as dynamic metadata above.
+
+Note that if multiple `Limit`s were exceeded by a request, only the `Limit` with the longest time until reset (i.e. its Retry-After value) will be available as dynamic metadata above. The only exception is if the `Limit` with the longest time until reset is `LogOnly` and there exists another non-`LogOnly` limit that was exceeded. In that case, the non-`LogOnly` `Limit` will be available as dynamic metadata. This ensures that `LogOnly` `Limits` will never prevent non-`LogOnly` `Limits` from enforcing or from being observable in the Envoy access log.
+
+### An example access log specification for RateLimit dynamic metadata
+
+Module:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ envoy_log_format: 'ratelimit %DYNAMIC_METADATA(envoy.filters.http.ratelimit:aes.ratelimit.name)% took action %DYNAMIC_METADATA(envoy.filters.http.ratelimit:aes.ratelimit.action)%'
+```
+
+## RateLimit examples
+
+### An example service-level rate limit
+
+The following `Mapping` resource will add a
+`my_default_generic_key_label` `generic_key` label to every request to
+the `foo-app` service:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: foo-app
+spec:
+ hostname: "*"
+ prefix: /foo/
+ service: foo
+ labels:
+ ambassador:
+ - label_group:
+ - generic_key:
+ value: my_default_generic_key_label
+```
+
+You can then create a default RateLimit for every request that matches
+this label:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: default-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern:
+ - generic_key: "my_default_generic_key_label"
+ rate: 10
+ unit: minute
+```
+
+> Tip: For testing purposes, it is helpful to configure per-minute
+> rate limits before switching the rate limits to per second or per
+> hour.
+
+### An example with multiple labels
+
+Mappings can have multiple `labels` which annotate a given request.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: catalog
+spec:
+ hostname: "*"
+ prefix: /catalog/
+ service: catalog
+ labels:
+ ambassador: # the label domain
+ - string_request_label: # the label group name -- useful for humans, ignored by Ambassador
+ - generic_key: # this is a generic_key label
+ value: catalog # annotate the request with `generic_key=catalog`
+ - header_request_label: # another label group name
+ - request_headers: # this is a label using request headers
+ key: headerkey # annotate the request with `headerkey=the specific HTTP method used`
+ header_name: ":method" # if the :method header is somehow unset, the whole group will be dropped.
+ - multi_request_label_group:
+ - request_headers:
+ key: authorityheader
+ header_name: ":authority"
+ - request_headers:
+ key: xuserheader
+ header_name: "x-user" # again, if x-user is not present, the _whole group_ is dropped
+```
+
+Let's digest the above example:
+
+* Request labels must be part of the "ambassador" label domain. Or
+ rather, it must match the domain in your
+ `RateLimitService.spec.domain` which defaults to
+ `Module.spec.default_label_domain` which defaults to `ambassador`;
+ but normally you should accept the default and just accept that the
+ domain on the Mappings must be set to "ambassador".
+* Each label must have a name, e.g., `one_request_label`
+* The `string_request_label` simply adds the string `catalog` to every
+ incoming request to the given mapping. The string is referenced
+ with the key `generic_key`.
+* The `header_request_label` adds a specific HTTP header value to the
+ request, in this case, the method. Note that HTTP/2 request headers
+ must be used here (e.g., the `host` header needs to be specified as
+ the `:authority` header).
+* Multiple labels can be part of a single named label, e.g.,
+ `multi_request_label` specifies two different headers to be added
+* When an HTTP header is not present, the entire named label is
+ omitted. The `omit_if_not_present: true` is an explicit notation to
+ remind end-users of this limitation. `false` is *not* a supported
+ value.
+
+### An example with multiple limits
+
+Labels can be grouped. This allows for a single request to count
+against multiple different `RateLimit` resources. For example,
+imagine the following scenario:
+
+1. Users should be limited on the total number of requests that can be
+ sent to a set of endpoints
+2. On a specific service, stricter limits are desirable
+
+The following `Mapping` resources could be configured:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: foo-app
+spec:
+ hostname: "*"
+ prefix: /foo/
+ service: foo
+ labels:
+ ambassador:
+ - foo-app_label_group:
+ - generic_key:
+ value: foo-app
+ - total_requests_group:
+ - remote_address
+ remote_address: {} # this is _required_ at present
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: bar-app
+spec:
+ hostname: "*"
+ prefix: /bar/
+ service: bar
+ labels:
+ ambassador:
+ - bar-app_label_group:
+ - generic_key:
+ value: bar-app
+ - total_requests_group:
+ - remote_address
+ remote_address: {} # this is _required_ at present
+```
+
+Now requests to the `foo-app` and the `bar-app` would be labeled with
+```yaml
+- "generic_key": "foo-app"
+- "remote_address": "10.10.11.12"
+```
+and
+```yaml
+- "generic_key": "bar-app"
+- "remote_address": "10.10.11.12"
+```
+respectively. `RateLimit`s on these two services could be created as
+such:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: foo-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern: [{generic_key: "foo-app"}]
+ rate: 10
+ unit: second
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: bar-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern: [{generic_key: "bar-app"}]
+ rate: 20
+ unit: second
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: user-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}]
+ rate: 100
+ unit: minute
+```
+
+### An example with global labels and groups
+
+Global labels are prepended to every single label group. In the above
+example, if the following global label was added in the `ambassador`
+Module:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ default_label_domain: ambassador
+ default_labels:
+ ambassador:
+ defaults:
+ - generic_key:
+ value: "my_default_label"
+```
+
+The labels metadata would change
+
+ - from
+ ```yaml
+ - "generic_key": "foo-app"
+ - "remote_address": "10.10.11.12"
+ ```
+ to
+ ```yaml
+ - "generic_key": "my_default_label"
+ - "generic_key": "foo-app"
+ - "remote_address": "10.10.11.12"
+ ```
+
+and
+
+ - from
+ ```yaml
+ - "generic_key": "bar-app"
+ - "remote_address": "10.10.11.12"
+ ```
+ to
+ ```yaml
+ - "generic_key": "my_default_label"
+ - "generic_key": "bar-app"
+ - "remote_address": "10.10.11.12"
+ ```
+
+respectively.
+
+And thus our `RateLimit`s would need to change to appropriately handle
+the new labels.
diff --git a/docs/edge-stack/3.9/tutorials/getting-started.md b/docs/edge-stack/3.9/tutorials/getting-started.md
new file mode 100644
index 000000000..f7f8a3481
--- /dev/null
+++ b/docs/edge-stack/3.9/tutorials/getting-started.md
@@ -0,0 +1,158 @@
+---
+description: "A simple three step guide to installing $productName$ and quickly get started routing traffic from the edge of your Kubernetes cluster to your services."
+---
+
+import Alert from '@material-ui/lab/Alert';
+import GettingStartedEdgeStack21Tabs from './gs-tabs'
+
+# $productName$ quick start
+
+
+
Contents
+
+- [1. Installation](#1-installation)
+- [Getting a license from Ambassador Cloud](#getting-a-license-from-ambassador-cloud)
+- [2. Routing traffic from the edge](#2-routing-traffic-from-the-edge)
+- [What's next?](#img-classos-logo-srcimageslogopng-whats-next)
+
+
+
+## 1. Installation
+
+### Getting a license from Ambassador Cloud
+
+We'll start by installing $productName$ into your cluster.
+
+$productName$ requires a [license](../../topics/using/licenses) to function, so the first step is getting one to use while installing. If you are in air-gapped environment, please [contact sales](https://www.getambassador.io/contact-us).
+
+1. Log in to [Ambassador Cloud](https://app.getambassador.io/cloud/edge-stack/license/existing/) with GitHub, GitLab or Google and select your team account.
+
+2. Follow the prompts to name the cluster and click **Generate Key**.
+
+3. Either follow the installation instructions there, or copy the token out and follow along here.
+
+4. Once your cluster is connected to Ambassador Cloud, a community license is automatically applied.
+
+**We recommend using Helm** to install but there are other options below to choose from. Please replace `` below with your token from Ambassador Cloud.
+
+
+
+Success! At this point, you have installed $productName$. Now let's get some traffic flowing to your services.
+
+## 2. Routing traffic from the edge
+
+$productName$ uses Kubernetes Custom Resource Definitions (CRDs) to declaratively define its desired state. The workflow you are going to build uses a simple demo app, a **`Listener` CRD**, and a **`Mapping` CRD**. The `Listener` CRD tells $productName$ what port to listen on, and the `Mapping` CRD tells $productName$ how to route incoming requests by host and URL path from the edge of your cluster to Kubernetes services.
+
+1. Start by creating a `Listener` resource for HTTP on port 8080:
+
+ ```
+ kubectl apply -f - <The Service and Deployment are created in your default namespace. You can use kubectl get services,deployments quote to see their status.
+
+3. Generate the YAML for a `Mapping` to tell $productName$ to route all traffic inbound to the `/backend/` path to the `quote` Service.
+
+ In this step, we'll be using the Mapping Editor, which you can find in the service details view of your [Ambassador Cloud connected installation](#getting-a-license-from-ambassador-cloud).
+ Open your browser to https://app.getambassador.io/cloud/services/quote/details and click on **New Mapping**.
+
+ Default options are automatically populated. **Enable and configure the following settings**, then click **Generate Mapping**:
+ - **Path Matching**: `/backend/`
+ - **OpenAPI Docs**: `/.ambassador-internal/openapi-docs`
+
+ 
+
+ Whether you decide to automatically push the change to Git for this newly create Mapping resource or not, the resulting Mapping should be similar to the example below.
+
+ **Apply this YAML to your target cluster now.**
+
+ ```yaml
+ kubectl apply -f - <Victory! You have created your first $productName$ Mapping, routing a request from your cluster's edge to a service!
+
+## What's next?
+
+Explore some of the popular tutorials on $productName$:
+
+* [Intro to Mappings](../../topics/using/intro-mappings/): declaratively routes traffic from
+the edge of your cluster to a Kubernetes service
+* [Host resource](../../topics/running/host-crd/): configure a hostname and TLS options for your ingress.
+* [Rate Limiting](../../topics/using/rate-limits/rate-limits/): create policies to control sustained traffic loads
+
+$productName$ has a comprehensive range of [features](/features/) to
+support the requirements of any edge microservice.
+
+To learn more about how $productName$ works, read the [$productName$ Story](../../about/why-ambassador).
diff --git a/docs/edge-stack/3.9/tutorials/gs-tabs.js b/docs/edge-stack/3.9/tutorials/gs-tabs.js
new file mode 100644
index 000000000..6cbeab1bc
--- /dev/null
+++ b/docs/edge-stack/3.9/tutorials/gs-tabs.js
@@ -0,0 +1,132 @@
+import AppBar from '@material-ui/core/AppBar';
+import Box from '@material-ui/core/Box';
+import Tab from '@material-ui/core/Tab';
+import Tabs from '@material-ui/core/Tabs';
+import { makeStyles } from '@material-ui/core/styles';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import CodeBlock from '../../../../../src/components/CodeBlock';
+import Icon from '../../../../../src/components/Icon';
+
+function TabPanel(props) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {/*Helm 3 token install instructions*/}
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Helm. The slideout contains instructions with a
+ unique cloud-connect-token that is used to connect your
+ cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'helm upgrade ambassador --namespace ambassador datawire/ambassador \\' +
+ '\n' +
+ ' --set agent.cloudConnectToken=$TOKEN && \\' +
+ '\n' +
+ 'kubectl -n ambassador wait --for condition=available --timeout=90s deploy -lproduct=aes'}
+
+
+
+
+ {/*Helm 2 token install instructions*/}
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Helm. The slideout contains instructions with a
+ unique cloud-connect-token that is used to connect your
+ cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'helm upgrade --namespace ambassador ambassador datawire/ambassador \\' +
+ '\n' +
+ ' --set crds.create=false --set agent.cloudConnectToken=$TOKEN && \\' +
+ '\n' +
+ 'kubectl -n ambassador wait --for condition=available --timeout=90s deploy -lproduct=aes'}
+
+
+
+
+ {/*YAML token install instructions*/}
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Kubernetes YAML. The slideout contains instructions
+ with a unique cloud-connect-token that is used to connect
+ your cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'kubectl create configmap -n ambassador ambassador-agent-cloud-token \\' +
+ '\n' +
+ ' --from-literal=CLOUD_CONNECT_TOKEN=$TOKEN'}
+
+
+
+
+ {/*edgectl token install instructions*/}
+ Connecting $productName$ that was installed via edgectl is
+ identical to the Kubernetes YAML procedure.
+
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Kubernetes YAML. The slideout contains instructions
+ with a unique cloud-connect-token that is used to connect
+ your cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'kubectl create configmap -n ambassador ambassador-agent-cloud-token \\' +
+ '\n' +
+ ' --from-literal=CLOUD_CONNECT_TOKEN=$TOKEN'}
+
+
+
+ );
+}
diff --git a/docs/edge-stack/3.9/versions.yml b/docs/edge-stack/3.9/versions.yml
new file mode 100644
index 000000000..5c35d393d
--- /dev/null
+++ b/docs/edge-stack/3.9/versions.yml
@@ -0,0 +1,35 @@
+# branch info
+branch: release/v3.9
+
+# self
+version: 3.9.0
+productName: "Ambassador Edge Stack"
+productNamePlural: "Ambassador Edge Stacks"
+productNamespace: ambassador
+productDeploymentName: edge-stack
+productHelmName: edge-stack
+
+# OSS (not self)
+ossVersion: 3.9.0
+ossDocsVersion: "3.9"
+ossChartVersion: 8.9.0
+OSSproductName: "Emissary-ingress"
+OSSproductNamePlural: "Emissary-ingresses"
+
+# AES (self)
+aesVersion: 3.9.0
+aesDocsVersion: "3.9"
+aesChartVersion: 8.9.0
+AESproductName: "Ambassador Edge Stack"
+AESproductNamePlural: "Ambassador Edge Stacks"
+
+# other products
+qotmVersion: 1.7
+quoteVersion: 0.5.0
+
+# Most recent version from previous major versions
+# This is mostly to ensure that the migration matrix stays up-to-date
+versionTwoX: 2.5.1
+chartVersionTwoX: 7.6.1
+versionOneX: 1.14.4
+chartVersionOneX: 6.9.5
diff --git a/docs/edge-stack/4.0-preview/custom-resources/filter-external.md b/docs/edge-stack/4.0-preview/custom-resources/filter-external.md
index a43f9aa10..6ce6d0586 100644
--- a/docs/edge-stack/4.0-preview/custom-resources/filter-external.md
+++ b/docs/edge-stack/4.0-preview/custom-resources/filter-external.md
@@ -42,6 +42,15 @@ spec:
include_body: IncludeBody # optional
maxBytes: int # required, default: `4096`
allowPartial: bool # required, default `true`
+ tlsConfig: TLSConfig # optional
+ certificate: TLSSource # required
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ caCertificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
status: []metav1.Condition # field managed by controller, max items: 8
```
@@ -57,6 +66,7 @@ status: []metav1.Condition # field managed by controller, max i
| `httpSettings` | [HTTPSettings][] | Settings specific to the http protocol. This can only be set when `protocol: "http"`. |
| `grpcSettings` | [GRPCSettings][] | Settings specific to the grpc protocol. This can only be set when `protocol: "grpc"`. |
| `include_body` | [IncludeBody][] | Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service. |
+| `tlsConfig` | [TLSConfig][] | Configures tls settings between $productName$ and the configured AuthService |
The overall Auth Service timeout that is configured in Envoy, mentioned in the timeout field is set to `5 Seconds` and is not currently configurable but will be made so for the official release of $productName$ 4.x.
@@ -100,6 +110,16 @@ Configures passing along the request body to the External Service. If not set th
| `maxBytes` | `int` | Sets the number of bytes of the request body to buffer over to the External Service |
| `allowPartial` | `bool` | Indicates whether the included body can be a partially buffered body or if the complete buffered body is expected. If not partial then a 413 http error is returned by Envoy. |
+### TLSConfig
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|-----------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `certificate.fromSecret` | SecretReference | Configures $productName$ to use the provided certificate to present to the server when connecting. Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret][] that contains the private key and public certificate that will be presented to the AuthService. Secret namespace defaults to Filter namespace if not set |
+| `caCertificate.fromSecret` | SecretReference | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret][] that contains the `tls.crt` key with the CA Certificate. Secret namespace defaults to Filter namespace if not set |
+
## External Filter Usage Guides
- [Using External Filters][]: Use the External Filter to write your own service with custom processing and authentication logic
@@ -110,6 +130,7 @@ Configures passing along the request body to the External Service. If not set th
[ExternalFilter]: #externalfilter
[GRPCSettings]: #grpcsettings
[IncludeBody]: #includebody
+[TLSConfig]: #tlsconfig
[usage guides section]: #external-filter-usage-guides
[ext_authz protocol]: ../../guides/custom-filters/ext-authz
[The Ext_Authz Protocol]: ../../guides/custom-filters/ext-authz
diff --git a/docs/edge-stack/4.0-preview/custom-resources/filter-oauth2.md b/docs/edge-stack/4.0-preview/custom-resources/filter-oauth2.md
index 92f4dc45c..124c0f81e 100644
--- a/docs/edge-stack/4.0-preview/custom-resources/filter-oauth2.md
+++ b/docs/edge-stack/4.0-preview/custom-resources/filter-oauth2.md
@@ -7,7 +7,7 @@ The OAuth2 Filter type performs OAuth2 authorization against an identity provide
This doc is an overview of all the fields on the OAuth2 `Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
Tutorials and guides for the OAuth2 `Filter` Resource can be found in the [usage guides section][].
-## External Filter API Reference
+## OAuth2 Filter API Reference
To create an OAuth2 Filter, the `spec.type` must be set to `oauth2`, and the `oauth2` field must contain the configuration for your
OAuth2 filter.
diff --git a/docs/edge-stack/latest b/docs/edge-stack/latest
deleted file mode 120000
index 98fccd6d0..000000000
--- a/docs/edge-stack/latest
+++ /dev/null
@@ -1 +0,0 @@
-3.8
\ No newline at end of file
diff --git a/docs/edge-stack/latest/about/aes-emissary-eol.md b/docs/edge-stack/latest/about/aes-emissary-eol.md
new file mode 100644
index 000000000..1e4b2caa9
--- /dev/null
+++ b/docs/edge-stack/latest/about/aes-emissary-eol.md
@@ -0,0 +1,56 @@
+# $productName$ End of Life Policy
+
+This document describes the End of Life policy and maintenance windows for Ambassador Edge Stack, and to the open source project Emissary Ingress.
+
+## Supported Versions
+
+Ambassador Edge Stack and Emissary-ingress versions are expressed as **x.y.z**, where **x** is the major version, **y** is the minor version, and **z** is the patch version, following [Semantic Versioning](https://semver.org/) terminology.
+
+**X-series (Major Versions)**
+
+- **1.y**: 1.0 GA on January 2020
+- **2.y**: 2.0.4 GA on October 2021, and 2.1.0 in December 2021.
+
+**Y-release (Minor versions)**
+
+- For 1.y, that is **1.14.z**
+- For 2.y, that is **2.3.z**
+
+In this document, **Current** refers to the latest X-series release.
+
+Maintenance refers to the previous X-series release, including security and Sev1 defect patches.
+
+## CNCF Ecosystem Considerations
+
+- Envoy releases a major version every 3 months and supports its previous releases for 12 months. Envoy does not support any release longer than 12 months.
+- Kubernetes 1.19 and newer receive 12 months of patch support (The [Kubernetes Yearly Support Period](https://github.com/kubernetes/enhancements/blob/master/keps/sig-release/1498-kubernetes-yearly-support-period/README.md)).
+
+# The Policy
+
+> We will offer a 6 month maintenance window for the latest Y-release of an X-series after a new X-series goes GA and becomes the current release. For example, we will support 2.3 for severity 1 and defect patches for six months after 3.0 is released.
+>
+
+> During the maintenance window, Y-releases will only receive security and Sev1 defect patches. Users desiring new features or bug fixes for lower severity defects will need to upgrade to the current X-series.
+>
+
+> The current X-series will receive as many Y-releases as necessary and as often as we have new features or patches to release.
+>
+
+> Ambassador Labs offers no-downtime migration to current versions from maintenance releases. Migration from releases that are outside of the maintenance window may be subject to downtime.
+>
+
+> Artifacts of releases outside of the maintenance window will be frozen and will remain available publicly for download with the best effort. These artifacts include Docker images, application binaries, Helm charts, etc.
+>
+
+### When we say support with “defect patches”, what do we mean?
+
+- We will fix security issues in our Emissary-ingress and Ambassador Edge Stack code
+- We will pick up security fixes from dependencies as they are made available
+- We will not maintain forks of our major dependencies
+- We will not attempt our own back ports of critical fixes to dependencies which are out of support from their own communities
+
+## Extended Maintenance for 1.14
+
+Given this policy, we should have dropped maintenance for 1.14 in March 2022, however we recognize that the introduction of an EOL policy necessitates a longer maintenance window. For this reason, we do offer an "extended maintenance" window for 1.14 until the end of September 2022, 3 months after the latest 2.3 release. Please note that this extended maintenance window will not apply to customers using Kubernetes 1.22 and above, and this extended maintenance will also not provide a no-downtime migration path from 1.14 to 3.0.
+
+After September 2022, the current series will be 3.x, and the maintenance series will be 2.y.
diff --git a/docs/edge-stack/latest/about/changes-2.x.md b/docs/edge-stack/latest/about/changes-2.x.md
new file mode 100644
index 000000000..89938a44b
--- /dev/null
+++ b/docs/edge-stack/latest/about/changes-2.x.md
@@ -0,0 +1,243 @@
+import Alert from '@material-ui/lab/Alert';
+
+Major Changes in $productName$ 2.X
+==================================
+
+The 2.X family introduces a number of changes to allow $productName$
+to more gracefully handle larger installations, reduce global configuration to
+better handle multitenant or multiorganizational installations, reduce memory
+footprint, and improve performance. We welcome feedback!! Join us on
+[Slack](http://a8r.io/slack) and let us know what you think.
+
+While $productName$ 2 is functionally compatible with $productName$ 1.14, note
+that this is a **major version change** and there are important differences between
+$productName$ 1.X and $productName$ $version$. For details, read on.
+
+## 1. Configuration API Version `getambassador.io/v3alpha1`
+
+$productName$ 2.0 introduced API version `getambassador.io/v3alpha1` to allow
+certain changes in configuration resources that are not backwards compatible with
+$productName$ 1.X. The most notable example of change is the addition of the
+**mandatory** `Listener` resource; however, there are important changes
+in `Host` and `Mapping` as well.
+
+
+ $productName$ 2.X supports only API versions getambassador.io/v2
+ and getambassador.io/v3alpha1. If you are using any resources with
+ older API versions, you will need to upgrade them.
+
+
+API version `getambassador.io/v3alpha1` replaces `x.getambassador.io/v3alpha1` from
+the 2.0 developer previews. `getambassador.io/v3alpha1` may still change as we receive
+feedback.
+
+## 2. Kubernetes 1.22 and Structural CRDs
+
+Kubernetes 1.22 requires [structural CRDs](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/).
+This change is primarily meant to support better CRD validation, but it also has the
+effect that union types are no longer allowed in CRDs: for example, an element that can be
+either a string or a list of strings is not allowed. Several such elements appeared in the
+`getambassador.io/v2` CRDs, requiring changes. In `getambassador.io/v3alpha1`:
+
+- `ambassador_id` must always be a list of strings
+- `Host.mappingSelector` supersedes `Host.selector`, and controls association between Hosts and Mappings
+- `Mapping.hostname` supersedes `Mapping.host` and `Mapping.host_regex`
+- `Mapping.tls` can only be a string
+- `Mapping.labels` always requires maps instead of strings
+
+## 2. `Listener`s, `Host`s, and `Mapping`s
+
+$productName$ 2.0 introduced the new **mandatory** `Listener` CRD, and made some changes
+to the `Host` and `Mapping` resources.
+
+### The `Listener` CRD
+
+The new [`Listener` CRD](../../topics/running/listener) defines where and how $productName$ should listen for requests from the network, and which `Host` definitions should be used to process those requests.
+
+**Note that `Listener`s are never created by $productName$, and must be defined by the user.** If you do not
+define any `Listener`s, $productName$ will not listen anywhere for connections, and therefore won't do
+anything useful. It will log a `WARNING` to this effect.
+
+A `Listener` specifically defines
+
+- `port`: a port number on which to listen for new requests;
+- `protocol` and `securityModel`: the protocol stack and security model to use (e.g. `HTTPS` using the `X-Forwarded-Proto` header); and
+- `hostBinding`: how to tell if a given `Host` should be associated with this `Listener`:
+ - a `Listener` can choose to consider all `Host`s, or only `Host`s in the same namespace as the `Listener`, or
+ - a `Listener` can choose to consider only `Host`s with a particular Kubernetes `label`.
+
+**Note that the `hostBinding ` is mandatory.** A `Listener` _must_ specify how to identify the `Host`s to associate with the `Listener`', or the `Listener` will be rejected. This is intended to help prevent cases where a `Listener` mistakenly grabs too many `Host`s: if you truly need a `Listener` that associates with all `Host`s, the easiest way is to tell the `Listener` to look for `Host`s in all namespaces, with no further selectors, for example:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: listener
+metadata:
+ name: all-hosts-listener
+spec:
+ port: 8080
+ securityModel: XFP
+ protocol: HTTPS
+ hostBinding:
+ namespace:
+ from: ALL
+```
+
+A `Listener` that has no associated `Host`s will be logged as a `WARNING`, and will not be included in the Envoy configuration generated by $productName$.
+
+Note also that there is no limit on how many `Listener`s may be created, and as such no limit on the number of ports to which a `Host` may be associated.
+
+
+ Learn more about Listener.
+ Learn more about Host.
+
+
+### Wildcard `Host`s No Longer Created
+
+In $productName$ 1.X, $productName$ would make sure that a wildcard `Host`, with a `hostname` of `"*"`, was always present.
+$productName$ 2.X does **not** force a wildcard `Host`: if you need the wildcard behavior, you will need to create
+a `Host` with a hostname of `"*"`.
+
+Of particular note is that $productName$ **will not** respond to queries to an IP address unless a wildcard
+`Host` is present. If `foo.example.com` resolves to `10.11.12.13`, and the only `Host` has a
+`hostname` of `foo.example.com`, then:
+
+- requests to `http://foo.example.com/` will work, but
+- requests to `http://10.11.12.13/` will **not** work.
+
+Adding a `Host` with a `hostname` of `"*"` will allow the second query to work.
+
+
+ Learn more about Host.
+
+
+### `Host` and `Mapping` Association
+
+The [`Host` CRD](../../topics/running/host-crd) continues to define information about hostnames, TLS certificates, and how to handle requests that are "secure" (using HTTPS) or "insecure" (using HTTP). The [`Mapping` CRD](../../topics/using/intro-mappings) continues to define how to map the URL space to upstream services.
+
+However, as of $productName$ 2.0, a `Mapping` will not be associated with a `Host` unless at least one of the following is true:
+
+- The `Mapping` specifies a `hostname` attribute that matches the `Host` in question.
+
+ - Note that a `getambassador.io/v2` `Mapping` has `host` and `host_regex`, rather than `hostname`.
+ - A `getambassador.io/v3alpha1` `Mapping` will honor `host` and `host_regex` as a transition aid, but `host` and `host_regex` are deprecated in favor of `hostname`.
+ - A `Mapping` that specifies `host_regex: true` will be associated with all `Host`s. This is generally far less desirable than using `hostname` with a DNS glob.
+
+- The `Host` specifies a `mappingSelector` that matches the `Mapping`'s Kubernetes `label`s.
+
+ - Note that a `getambassador.io/v2` `Host` has a `selector`, rather than a `mappingSelector`.
+ - A `getambassador.io/v3alpha1` `Host` ignores `selector` and, instead, looks only at `mappingSelector`.
+ - Where a `selector` got a default value if not specified, `mappingSelector` must be explicitly stated.
+
+Without either a `hostname` match or a `label` match, the `Mapping` will not be associated with the `Host` in question. This is intended to help manage memory consumption with large numbers of `Host`s and large numbers of `Mapping`s.
+
+
+ Learn more about Host.
+ Learn more about Mapping.
+
+
+### Independent `Host` Actions
+
+Each `Host` can specify its `requestPolicy.insecure.action` independently of any other `Host`, allowing for HTTP routing as flexible as HTTPS routing.
+
+
+ Learn more about Host.
+
+
+### `Host`, `TLSContext`, and TLS Termination
+
+As of $productName$ 2.0, **`Host`s are required for TLS termination**. It is no longer sufficient to create a [`TLSContext`](../../topics/running/tls/#tlscontext) by itself; the [`Host`](../../topics/running/host-crd) is required.
+
+The minimal setup for TLS termination is therefore a Kubernetes `Secret` of type `kubernetes.io/tls`, and a `Host` that uses it:
+
+```yaml
+---
+kind: Secret
+type: kubernetes.io/tls
+metadata:
+ name: minimal-secret
+data:
+ tls secret goes here
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: minimal-host
+spec:
+ hostname: minimal.example.com
+ tlsSecret:
+ name: minimal-secret
+```
+
+It is **not** necessary to explicitly state a `TLSContext` in the `Host`: setting `tlsSecret` is enough. Of course, `TLSContext` is still the ideal way to share TLS configuration between more than one `Host`. For further examples, see [Configuring $productName$ Communications](../../howtos/configure-communications).
+
+
+ Learn more about Host.
+ Learn more about TLSContext.
+
+
+### `Mapping`s, `TCPMapping`s, and TLS Origination
+
+A `getambassador.io/v2` `Mapping` or `TCPMapping` could specify `tls: true` to indicate TLS origination without supplying a certificate. This is not supported in `getambassador.io/v3alpha1`: instead, use an `https://` prefix on the `service`. In the [Mapping](../../topics/using/mappings/#using-tls), this is straightforward, but [there are more details for the `TCPMapping` when using TLS](../../topics/using/tcpmappings/#tcpmapping-and-tls).
+
+
+ Learn more about Mapping.
+
+
+### `Mapping`s and `labels`
+
+The `Mapping` CRD includes a `labels` field, used with rate limiting. The
+[syntax of the `labels`](../../topics/using/rate-limits#attaching-labels-to-requests) has changed
+for compatibility with Kubernetes 1.22.
+
+
+ Learn more about Mapping.
+
+
+### `Host`s and ACME
+
+In $productName$ 2.0, ACME will be disabled if a `Host` does not set `acmeProvider` at all (prior to $productName$ 2.0, not mentioning `acmeProvider` would result in the ACME client attempting, and failing, to start). If `acmeProvider` is set, but `acmeProvider.authority` is not set, the ACME client will continue to default to Let's Encrypt, in order to preserve compatibility with $productName$ prior to $productName$ 2.0. For further examples, see [Configuring $productName$ to Communicate](../../howtos/configure-communications).
+
+
+ Learn more about Host.
+
+
+## 3. Other Changes
+
+### Envoy V3 API by Default
+
+By default, $productName$ 2.X will configure Envoy using the
+[V3 Envoy API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/api).
+
+### More Performant Reconfiguration by Default
+
+In $productName$ 1.X, the environment variable `AMBASSADOR_FAST_RECONFIGURE` could be used to enable a higher performance implementation of the code $productName$ uses to validate and generate Envoy configuration. In $productName$ 2.X, this higher-performance mode is always enabled.
+
+### Changes to the `ambassador` `Module`, and the `tls` `Module`
+
+It is no longer possible to configure TLS using the `tls` element of the `ambassador` `Module` or using the `tls` `Module`. Both of these cases are correctly covered by the `TLSContext` resource.
+
+With the introduction of the `Listener` resource, a few settings have moved from the `Module` to the `Listener`.
+
+Configuration for the `PROXY` protocol is part of the `Listener` resource in $productName$ 2.X, so the `use_proxy_protocol` element of the `ambassador` `Module` is no longer supported. Note that the `Listener` resource can configure `PROXY` resource per-`Listener`, rather than having a single global setting. For further information, see the [`Listener` documentation](../../topics/running/listener).
+
+`xff_num_trusted_hops` has been removed from the `Module`, and its functionality has been moved to the `l7Depth` setting in the `Listener` resource.
+
+
+ Learn more about Listener.
+
+
+### `TLSContext` `redirect_cleartext_from` and `Host` `insecure.additionalPort`
+
+`redirect_cleartext_from` has been removed from the `TLSContext` resource; `insecure.additionalPort` has been removed from the `Host` CRD. Both of these cases are covered by adding additional `Listener`s. For further examples, see [Configuring $productName$ Communications](../../howtos/configure-communications).
+
+### Service Preview No Longer Supported
+
+Service Preview is no longer supported as of $productName$ 2.X, as its use cases are supported by Telepresence.
+
+### Edge Policy Console No Longer Supported
+
+The Edge Policy Console has been removed as of $productName$ 2.X, in favor of Ambassador Cloud.
+
+### `Project` CRD No Longer Supported
+
+The `Project` CRD has been removed as of $productName$ 2.X, in favor of Argo.
diff --git a/docs/edge-stack/latest/about/changes-3.y.md b/docs/edge-stack/latest/about/changes-3.y.md
new file mode 100644
index 000000000..fddc2b62e
--- /dev/null
+++ b/docs/edge-stack/latest/about/changes-3.y.md
@@ -0,0 +1,56 @@
+import Alert from '@material-ui/lab/Alert';
+
+Major Changes in $productName$ 3.X
+==================================
+
+The 3.X family introduces a number of changes to ensure $productName$
+keeps up with latest Envoy versions and to support new features such as HTTP/3.
+We welcome feedback! Join us on [Slack](http://a8r.io/slack) and let us know what you think.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+## 1. Envoy Upgraded to 1.22
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy **1.22** which keeps $productName$ up-to-date with
+the latest security fixes, bug fixes, performance improvements and feature enhancements provided by Envoy Proxy. Most of the changes are under the hood but the most notable change to developers is the removal of support for Envoy V2 Transport Protocol. This means all external filters and LogServices must be updated to use the V3 Protocol.
+
+This also means some of the v2 runtime bootstrap flags have been removed as well:
+
+```yaml
+# No longer necessary because this was removed from Envoy
+# $productName$ already was converted to use the compressor API
+# https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+"envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+# Upgraded to v3, all support for V2 Transport Protocol removed
+"envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+"envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+# Developer will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+"envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+# V2 protocol removed so flag no longer necessary
+"envoy.reloadable_features.enable_deprecated_v2_api": true,
+```
+
+
+ Learn more about Envoy Proxy changes.
+
+
+## 2. Envoy V2 xDS Transport Protocol Support Removed
+
+With the upgrade to Envoy **1.22**, the V2 Envoy Transport Protocol is no longer supported and has been removed.
+$productName$ 3.X **only** supports [V3 Envoy API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/api).
+
+The `AuthService`, `RatelimitService`, `LogService` and `ExternalFilters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+
+## 3. Envoy V2 xDS Configration Support Removed
+
+Envoy can no longer be configured to use the v2 xDS configuration and will always use v3 xDS configuration. This change removes the AMBASSADOR_ENVOY_API_VERSION because it no longer configurable and will have no effect.
+
+
+## 4. Zipkin HTTP_JSON_V1 support is removed
+
+Envoy removed support for the older `HTTP_JSON_V1` collector_endpoint_version. If using the `zipkin` driver with the `TracingService`,
+then you will have to update it to use `HTTP_JSON` or `HTTP_PROTO`.
diff --git a/docs/edge-stack/latest/about/faq.md b/docs/edge-stack/latest/about/faq.md
new file mode 100644
index 000000000..14528f9a1
--- /dev/null
+++ b/docs/edge-stack/latest/about/faq.md
@@ -0,0 +1,88 @@
+# Frequently Asked Questions
+
+## General
+
+### Why $productName$?
+
+Kubernetes shifts application architecture for microservices, as well as the
+development workflow for a full-cycle development. $productName$ is designed for
+the Kubernetes world with:
+
+* Sophisticated traffic management capabilities (thanks to its use of [Envoy Proxy](https://www.envoyproxy.io)), such as load balancing, circuit breakers, rate limits, and automatic retries.
+* API management capabilities such as a developer portal and OpenID Connect integration for Single Sign-On.
+* A declarative, self-service management model built on Kubernetes Custom Resource Definitions, enabling GitOps-style continuous delivery workflows.
+
+We've written about [the history of $productName$](https://blog.getambassador.io/building-ambassador-an-open-source-api-gateway-on-kubernetes-and-envoy-ed01ed520844), [Why $productName$ In Depth](../why-ambassador), [Features and Benefits](../features-and-benefits) and about the [evolution of API Gateways](../../topics/concepts/microservices-api-gateways/).
+
+### What's the difference between $OSSproductName$ and $AESproductName$?
+
+$OSSproductName$ is a CNCF Incubating project and provides the open-source core of $AESproductName$. Originally we called $OSSproductName$ the "Ambassador API Gateway", but as the project evolved, we realized that the functionality we were building had extended far beyond an API Gateway. In particular, the $AESproductName$ is intended to provide all the functionality you need at the edge -- hence, an "edge stack." This includes an API Gateway, ingress controller, load balancer, developer portal, and more.
+
+### How is $AESproductName$ licensed?
+
+The core $OSSproductName$ is open source under the Apache Software License 2.0. The GitHub repository for the core is [https://github.com/emissary-ingress/emissary](https://github.com/emissary-ingress/emissary). Some additional features of the $AESproductName$ (e.g., Single Sign-On) are not open source and available under a proprietary license.
+
+### Can I use the add-on features for $AESproductName$ for free?
+
+Yes! For more details please see the [$productName$ Licenses page](../../topics/using/licenses).
+
+### How does $productName$ use Envoy Proxy?
+
+$productName$ uses [Envoy Proxy](https://www.envoyproxy.io) as its core proxy. Envoy is an open-source, high-performance proxy originally written by Lyft. Envoy is now part of the Cloud Native Computing Foundation.
+
+### Is $productName$ production ready?
+
+Yes. Thousands of organizations, large and small, run $productName$ in production.
+Public users include Chick-Fil-A, ADP, Microsoft, NVidia, and AppDirect, among others.
+
+### What is the performance of $productName$?
+
+There are many dimensions to performance. We published a benchmark of [$productName$ performance on Kubernetes](/resources/envoyproxy-performance-on-k8s/). Our internal performance regressions cover many other scenarios; we expect to publish more data in the future.
+
+### What's the difference between a service mesh (such as Istio) and $productName$?
+
+Service meshes focus on routing internal traffic from service to service
+("east-west"). $productName$ focuses on traffic into your cluster ("north-south").
+While both a service mesh and $productName$ can route L7 traffic, the reality is that
+these use cases are quite different. Many users will integrate $productName$ with a
+service mesh. Production customers of $productName$ have integrated with Consul,
+Istio, and Linkerd2.
+
+## Common Configurations
+
+### How do I disable the 404 landing page?
+
+See the [Controlling the $productName$ 404 Page](../../howtos/controlling-404) how-to.
+
+### How do I disable the default Admin mappings?
+
+See the [Protecting the Diagnostics Interface](../../howtos/protecting-diag-access) how-to.
+
+## Troubleshooting
+
+### How do I get help for $productName$?
+
+We have an online [Slack community](http://a8r.io/slack) with thousands of
+users. We try to help out as often as possible, although we can't promise a
+particular response time. If you need a guaranteed SLA, we also have commercial
+contracts. [Contact sales](/contact-us/) for more information.
+
+### What do I do when I get the error `no healthy upstream`?
+
+This error means that $productName$ could not connect to your backend service.
+Start by verifying that your backend service is actually available and
+responding by sending an HTTP response directly to the pod. Then, verify that
+$productName$ is routing by deploying a test service and seeing if the mapping
+works. Then, verify that your load balancer is properly routing requests to
+$productName$. In general, verifying each network hop between your client and
+backend service is critical to finding the source of the problem.
+
+### What is the difference between the v3alpha1 and v1alpha1 CRDs?
+
+There are two different CRD versions supported by $productName$.
+The first are the `getambassador.io/v3alpha1` CRDs which were introduced with
+$productName$ 2.x. These are still supported and are not deprecated. As of $productName$ $version$, the new `gateway.getambassador.io/v1alpha1` CRDs have also been introduced.
+The `v1alpha1` CRDs have not only a new version, but also a new apigoup so that way they can
+be installed alongside the older CRDs without causing any conflicts.
+
+The `v1alpha1` CRDs are only available for the `Filter`, `FilterPolicy`, `WebApplicationFirewall`, and `WebApplicationFirewallPolicy` resources, and are the next generation of the CRDs that $productName$ will support. We are introducing them now to allow users to try them out without needing to stop using the `v3alpha1` CRDs. You can use `v1alpha1` and `v3alpha1` CRDs in the same cluster at the same time, but `FilterPolicies` are not able to reference `Filters` that do not match their CRD version.
diff --git a/docs/edge-stack/latest/about/features-and-benefits.md b/docs/edge-stack/latest/about/features-and-benefits.md
new file mode 100644
index 000000000..ecad16175
--- /dev/null
+++ b/docs/edge-stack/latest/about/features-and-benefits.md
@@ -0,0 +1,39 @@
+# Features and benefits
+
+In cloud-native organizations, developers frequently take on responsibility for the full development lifecycle of a service, from development to QA to operations. $productName$ was specifically designed for these organizations where developers have operational responsibility for their service(s).
+
+As such, the $productName$ is designed to be used by both developers and operators.
+
+## Self-Service via Kubernetes Annotations
+
+$productName$ is built from the start to support _self-service_ deployments -- a developer working on a new service doesn't have to go to Operations to get their service added to the mesh, they can do it themselves in a matter of seconds. Likewise, a developer can remove their service from the mesh, or merge services, or separate services, as needed, at their convenience. All of these operations are performed via Kubernetes resources or annotations, so they can easily integrate with your existing development workflow.
+
+## Flexible canary deployments
+
+Canary deployments are an essential component of cloud-native development workflows. In a canary deployment, a small percentage of production traffic is routed to a new version of a service to test it under real-world conditions. $productName$ allows developers to easily control and manage the amount of traffic routed to a given service through annotations. [This tutorial](https://www.datawire.io/faster/canary-workflow/) covers a complete canary workflow using the $productName$.
+
+## Kubernetes-native architecture
+
+$productName$ relies entirely on Kubernetes for reliability, availability, and scalability. For example, $productName$ persists all state in Kubernetes, instead of requiring a separate database. Scaling the $productName$ is as simple as changing the replicas in your deployment, or using a [horizontal pod autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/).
+
+$productName$ uses [Envoy](https://www.envoyproxy.io) for all traffic routing and proxying. Envoy is a modern L7 proxy that is used in production at companies including Lyft, Apple, Google, and Stripe.
+
+## gRPC and HTTP/2 support
+
+$productName$ fully supports gRPC and HTTP/2 routing, thanks to Envoy's extensive capabilities in this area. See [gRPC and $productName$](../../howtos/grpc) for more information.
+
+## Istio Integration
+
+$productName$ integrates with the [Istio](https://istio.io) service mesh as the edge proxy. In this configuration, $productName$ routes external traffic to the internal Istio service mesh. See [Istio and $productName$](../../howtos/istio) for details.
+
+## Authentication
+
+$productName$ supports authenticating incoming requests with a custom authentication service, OAuth/OpenID Connect, or JWT. When configured, the $productName$ will check with a third party authentication service prior to routing an incoming request. For more information, see the [authentication guide](../../topics/using/filters/).
+
+## Rate limiting
+
+$productName$ supports rate limiting incoming requests. When configured, the $productName$ will check with a third party rate limit service prior to routing an incoming request. For more information, see the [rate limiting guide](../../topics/using/rate-limits/).
+
+## Integrated UI
+
+$productName$ includes a diagnostics service so that you can quickly debug issues associated with configuring the $productName$. For more information, see [running $productName$ in Production](../../topics/running).
diff --git a/docs/edge-stack/latest/about/known-issues.md b/docs/edge-stack/latest/about/known-issues.md
new file mode 100644
index 000000000..4a5c45f5b
--- /dev/null
+++ b/docs/edge-stack/latest/about/known-issues.md
@@ -0,0 +1,20 @@
+import Alert from '@material-ui/lab/Alert';
+
+Known Issues in $productName$
+=============================
+
+## 2.2.1
+
+- TLS certificates using elliptic curves were incorrectly flagged as invalid. This issue is
+ corrected in $productName$ 2.2.2.
+
+## 2.2.0
+
+- If $productName$'s Pods start before Redis is responding, it may be necessary to restart
+ $productName$ for rate limiting to function correctly.
+
+- When using the ACME client provided with $productName$, a delayed ACME response can
+ prevent the `Host` using ACME from becoming active.
+
+ - Workaround: Make sure you have a wildcard `Host` that does not use ACME. The insecure routing
+ action doesn't matter: it's fine for this `Host` to redirect or even reject insecure requests.
diff --git a/docs/edge-stack/latest/about/why-ambassador.md b/docs/edge-stack/latest/about/why-ambassador.md
new file mode 100644
index 000000000..f16def3a1
--- /dev/null
+++ b/docs/edge-stack/latest/about/why-ambassador.md
@@ -0,0 +1,54 @@
+# Why $productName$?
+
+$productName$ gives platform engineers a comprehensive, self-service edge stack for managing the boundary between end-users and Kubernetes. Built on the [Envoy Proxy](https://www.envoyproxy.io) and fully Kubernetes-native, $productName$ is made to support multiple, independent teams that need to rapidly publish, monitor, and update services for end-users. A true edge stack, $productName$ can also be used to handle the functions of an API Gateway, a Kubernetes ingress controller, and a layer 7 load balancer (for more, see [this blog post](https://blog.getambassador.io/kubernetes-ingress-nodeport-load-balancers-and-ingress-controllers-6e29f1c44f2d)).
+
+## How Does $productName$ work?
+
+$productName$ is a Kubernetes-native [microservices API gateway](../../topics/concepts/microservices-api-gateways) built on the open core of $OSSproductName$ and the [Envoy Proxy](https://www.envoyproxy.io). $productName$ is built from the ground up to support multiple, independent teams that need to rapidly publish, monitor, and update services for end-users. $productName$ can also be used to handle the functions of a Kubernetes ingress controller and load balancer (for more, see [this blog post](https://blog.getambassador.io/kubernetes-ingress-nodeport-load-balancers-and-ingress-controllers-6e29f1c44f2d)).
+
+## Cloud-native applications today
+
+Traditional cloud applications were built using a monolithic approach. These applications were designed, coded, and deployed as a single unit. Today's cloud-native applications, by contrast, consist of many individual (micro)services. This results in an architecture that is:
+
+* __Heterogeneous__: Services are implemented using multiple (polyglot) languages, they are designed using multiple architecture styles, and they communicate with each other over multiple protocols.
+* __Dynamic__: Services are frequently updated and released (often without coordination), which results in a constantly-changing application.
+* __Decentralized__: Services are managed by independent product-focused teams, with different development workflows and release cadences.
+
+### Heterogeneous services
+
+$productName$ is commonly used to route traffic to a wide variety of services. It supports:
+
+* configuration on a *per-service* basis, enabling fine-grained control of timeouts, rate limiting, authentication policies, and more.
+* a wide range of L7 protocols natively, including HTTP, HTTP/2, gRPC, gRPC-Web, and WebSockets.
+* Can route raw TCP for services that use protocols not directly supported by $productName$.
+
+### Dynamic services
+
+Service updates result in a constantly changing application. The dynamic nature of cloud-native applications introduces new challenges around configuration updates, release, and testing. $productName$:
+
+* Enables [progressive delivery](../../topics/concepts/progressive-delivery), with support for canary routing and traffic shadowing.
+* Exposes high-resolution observability metrics, providing insight into service behavior.
+* Uses a zero downtime configuration architecture, so configuration changes have no end-user impact.
+
+### Decentralized workflows
+
+Independent teams can create their own workflows for developing and releasing functionality that are optimized for their specific service(s). With $productName$, teams can:
+
+* Leverage a [declarative configuration model](../../topics/concepts/gitops-continuous-delivery), making it easy to understand the canonical configuration and implement GitOps-style best practices.
+* Independently configure different aspects of $productName$, eliminating the need to request configuration changes through a centralized operations team.
+
+## $productName$ is engineered for Kubernetes
+
+$productName$ takes full advantage of Kubernetes and Envoy Proxy.
+
+* All of the state required for $productName$ is stored directly in Kubernetes, eliminating the need for an additional database.
+* The $productName$ team has added extensive engineering efforts and integration testing to ensure optimal performance and scale of Envoy and Kubernetes.
+
+## For more information
+
+[Deploy $productName$ today](../../tutorials/getting-started) and join the community [Slack Channel](http://a8r.io/slack).
+
+Interested in learning more?
+
+* [Why did we start building $productName$?](https://blog.getambassador.io/building-ambassador-an-open-source-api-gateway-on-kubernetes-and-envoy-ed01ed520844)
+* [$productName$ Architecture overview](../../topics/concepts/architecture)
diff --git a/docs/edge-stack/latest/aes-pages.yml b/docs/edge-stack/latest/aes-pages.yml
new file mode 100644
index 000000000..bd03e5c39
--- /dev/null
+++ b/docs/edge-stack/latest/aes-pages.yml
@@ -0,0 +1,24 @@
+# AES pages should be represented in a yaml array with the pathnames as the value
+# Ex:
+# - /docs
+# - /reference
+#
+
+/topics/using/filters/
+/topics/using/filters/oauth2
+/topics/using/filters/jwt
+/topics/using/filters/external
+/topics/using/filters/plugin
+/topics/using/edgectl/
+/topics/using/edgectl/edge-control
+/topics/using/edgectl/edge-control-in-ci
+/topics/using/edgectl/service-preview-install
+/topics/using/edgectl/service-preview-reference
+/topics/using/edgectl/service-preview-tutorial
+/topics/using/dev-portal
+/topics/using/edge-policy-console
+/topics/using/rate-limits/rate-limits/
+/topics/running/aes-redis
+/topics/running/aes-extensions/
+/topics/running/aes-extensions/authentication
+/topics/running/aes-extensions/ratelimit
diff --git a/docs/edge-stack/latest/api-gateway-pages.yml b/docs/edge-stack/latest/api-gateway-pages.yml
new file mode 100644
index 000000000..b9010fdbc
--- /dev/null
+++ b/docs/edge-stack/latest/api-gateway-pages.yml
@@ -0,0 +1,72 @@
+# API Gateway pages should be represented in a yaml array with the pathnames as the value
+# Ex:
+# - /docs
+# - /reference
+#
+- /docs/dev-guide/canary-release-concepts
+- /docs/dev-guide/test-in-prod
+- /docs/guides/
+- /reference/add_request_headers
+- /reference/add_response_headers
+- /reference/ambassador-with-aws
+- /reference/canary
+- /reference/circuit-breakers
+- /reference/configuration
+- /reference/core/ambassador
+- /reference/core/crds
+- /reference/core/ingress-controller
+- /reference/core/load-balancer
+- /reference/core/resolvers
+- /reference/core/tls
+- /reference/cors
+- /reference/diagnostics
+- /reference/gzip
+- /reference/headers
+- /reference/host
+- /reference/host-crd
+- /reference/ambassadormappings
+- /reference/modules
+- /reference/prefix_regex
+- /reference/rate-limits
+- /reference/redirects
+- /reference/remove_request_headers
+- /reference/remove_response_headers
+- /reference/retries
+- /reference/rewrites
+- /reference/running
+- /reference/services/auth-service
+- /reference/services/log-service
+- /reference/services/rate-limit-service
+- /reference/services/services
+- /reference/services/tracing-service
+- /reference/shadowing
+- /reference/statistics
+- /reference/tcpmappings
+- /reference/timeouts
+- /reference/tls/cleartext-redirection
+- /reference/tls/client-cert-validation
+- /reference/tls/mtls
+- /reference/tls/origination
+- /user-guide/auth-tutorial
+- /user-guide/bare-metal
+- /user-guide/cd-declarative-gitops
+- /user-guide/cert-manager
+- /user-guide/consul
+- /user-guide/early-access
+- /user-guide/gitops-ambassador
+- /user-guide/grpc
+- /user-guide/helm
+- /user-guide/install-ambassador-oss
+- /user-guide/knative
+- /user-guide/linkerd2
+- /user-guide/monitoring
+- /user-guide/rate-limiting
+- /user-guide/rate-limiting-tutorial
+- /user-guide/security
+- /user-guide/sni
+- /user-guide/tls-termination
+- /user-guide/tracing-tutorial
+- /user-guide/tracing-tutorial-datadog
+- /user-guide/tracing-tutorial-zipkin
+- /user-guide/websockets-ambassador
+- /user-guide/with-istio
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md
new file mode 100644
index 000000000..2e99bc239
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md
@@ -0,0 +1,74 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **APIKey Filter** Type (v1alpha1)
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied. For more information about how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `APIKey Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 APIKey Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## APIKey Filter API Reference
+
+To create an APIKey Filter, the `spec.type` must be set to `apikey`, and the `apikey` field must contain the configuration for your
+APIKey Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-apikey-filter"
+ namespace: "example-namespace"
+spec:
+ type: "apikey" # required
+ apikey: APIKeyFilter # required when `type: "apikey"`
+ httpHeader: string # optional, default: `x-api-key`
+ keys: []APIKeyItem # required, min items: 1
+ - secretName: string # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### APIKeyFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `httpHeader` | `string` | The name of the http header where the api-key will be found (always case-insensitive). By default it will use the `x-api-key` header. |
+| `keys` | \[\][APIKeyItem][] | The set of APIKeys that are used to check the whether the incoming request is valid. |
+
+### APIKeyItem
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `secretName` | `string` | Defines how to resolve the values of the keys. Currently the only supported way to resolve a key is via a local secret. APIKeys cannot use shared secrets in a different namespace than the `APIKey Filter` resource. |
+
+**Note about Secret formatting**:
+When supplying secrets to an API Key filter, the keys of the Secret do not matter, but the value of your API Key must be [base64][] encoded.
+
+For example, if you want to create a secret for the API Key value `example-api-key-value`, the secret should look like:
+
+```yaml
+---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apikey-filter-keys
+ type: Opaque
+ data:
+ any-name-you-want: ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+```
+
+You can specify as many API Keys in the Secret as you like.
+
+[APIKeyItem]: #apikeyitem
+[FilterPolicy Resource]: ../filterpolicy
+[base64]: https://en.wikipedia.org/wiki/Base64
+[the v3alpha1 APIKey Filter api reference]: ../../../getambassador/v3alpha1/filter-apikey
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-external.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-external.md
new file mode 100644
index 000000000..42611ee18
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-external.md
@@ -0,0 +1,138 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **External Filter** Type (v1alpha1)
+
+The `External Filter` allows users to provide their own Kubernetes Service speaking the [ext_authz protocol][].
+$productName$ will send a request to this "External Service" that contains a copy of the incoming request. The External Service will then be able
+to examine details of the incoming request, make changes to its headers, and allow or reject it by sending back a response to $productName$.
+The external service is free to perform any logic it likes before responding to $productName$, allowing for custom filtering and
+processing on incoming requests. The `External Filter` may be used along with any of the other Filter types. For more information about
+how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `External Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `External Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 External Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## External Filter API Reference
+
+To create an External Filter, the `spec.type` must be set to `external`, and the `external` field must contain the configuration for your
+external filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-external-filter"
+ namespace: "example-namespace"
+spec:
+ type: "external" # required
+ external: ExternalFilter # required when `type: "external"`
+ protocol: Enum # required
+ authServiceURL: string # required, must be an absolute url
+ statusOnError: int # optional, default: `403`
+ failureModeAllow: bool # optional, default: `false`
+ timeout: Duration # optional, default: `"5s"`
+ httpSettings: HTTPSettings # optional, can only be set when `protocol: "http"`
+ pathPrefix: string # optional
+ allowedRequestHeaders: []string # optional
+ allowedAuthorizationHeaders: []string # optional
+ addLinkerdHeaders: bool # optional, default: `false`
+ grpcSettings: GRPCSettings # optional, can only be set when `protocol: "grpc"`
+ protocolVersion: Enum # optional, default: `"v3"`
+ include_body: IncludeBody # optional
+ maxBytes: int # required, default: `4096`
+ allowPartial: bool # required, default `true`
+ tlsConfig: TLSConfig # optional
+ certificate: TLSSource # required
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ caCertificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### ExternalFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `protocol` | `Enum` (`"http"`/`"grpc"`) | The type of protocol to use when communicating with the External Service. It is recommended to use "grpc" over "http" due to supporting additional capabilities. |
+| `authServiceURL` | `string` | The URL of the service performing the authorization / filtering logic. Must be an absolute URL. |
+| `statusOnError` | `int` | Allows overriding the status code returned when the External Service returns a non 200 response code for `protocol: "http"` or [DeniedHttpResponse][] for `protocol: "grpc"` |
+| `failureModeAllow` | `bool` | Determines what happens when $productName$ cannot communicate with the External Service due to network issues, or the service not being available. By default, the ExternalFilter will reject the request if it is unable to communicate. This can be overriden by setting this setting to `"true"` so that it fails open, allowing the request through to the upstream service. |
+| `timeout` | [Duration][] | The amount of time $productName$ will wait before erring on a timeout. **Note**: this value cannot be larger than the overall Auth Service timeout that is configured in Envoy or else it would effectively not have any timeout. |
+| `httpSettings` | [HTTPSettings][] | Settings specific to the http protocol. This can only be set when `protocol: "http"`. |
+| `grpcSettings` | [GRPCSettings][] | Settings specific to the grpc protocol. This can only be set when `protocol: "grpc"`. |
+| `include_body` | [IncludeBody][] | Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service. |
+| `tlsConfig` | [TLSConfig][] | Configures tls settings between $productName$ and the configured AuthService |
+
+### Duration
+
+**Appears On**: [ExternalFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### HTTPSettings
+
+**Appears On**:
+Settings specific to the http protocol. This can only be set when `protocol: "http"`.
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `pathPrefix` | `string` | Value that gets appended to the path of the downsteam request. Nothing is appended when this field is omitted |
+| `allowedRequestHeaders` | `[]string` | A list of headers from the downstream request that will be passed along as headers in the request to the external service. This includes metadata sent from Envoy to the EdgeStack Auth Service. By default, the following list of headers are passed through: `authorization`,`cookie`,`from`,`proxy-authorization`, `user-agent`, `x-forwarded-for`, `x-forwarded-host`, `x-forwarded-proto`. |
+| `allowedAuthorizationHeaders` | `[]string` | Headers from the External Service that will be added to the request to the upstream service. By default, the following headers are passed to the upstream service: `location`,`authorization`,`proxy-authenticate`,`set-cookie`,`www-authenticate`. Any additional headers that are needed should be added and are case-insenstive. |
+| `addLinkerdHeaders` | `bool` | When set to `true`, injects the `l5d-dst-override` header set to hostname and port of the external service which is used by [LinkerD][] when proxying through the Service Mesh. |
+
+### GRPCSettings
+
+**Appears On**: [ExternalFilter][]
+Settings specific to the http protocol. This can only be set when `protocol: "grpc"`.
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `protocolVersion` | `Enum` (`"v3"`) | Indicates the version of the transport protocol that the External Filter is using. This is only applicable to External Filters using `protocol: "grpc"`. Currently the only supported version is `"v3"`, so this field exists to provide compatability for future verions of ext_authz. |
+
+### IncludeBody
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `maxBytes` | `int` | Sets the number of bytes of the request body to buffer over to the External Service |
+| `allowPartial` | `bool` | Indicates whether the included body can be a partially buffered body or if the complete buffered body is expected. If not partial then a 413 http error is returned by Envoy. |
+
+### TLSConfig
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|-----------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `certificate.fromSecret` | SecretReference | Configures $productName$ to use the provided certificate to present to the server when connecting. Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret][] that contains the private key and public certificate that will be presented to the AuthService. Secret namespace defaults to Filter namespace if not set |
+| `caCertificate.fromSecret` | SecretReference | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret][] that contains the `tls.crt` key with the CA Certificate. Secret namespace defaults to Filter namespace if not set |
+
+[Duration]: #duration
+[HTTPSettings]: #httpsettings
+[ExternalFilter]: #externalfilter
+[GRPCSettings]: #grpcsettings
+[IncludeBody]: #includebody
+[TLSConfig]: #tlsconfig
+[the v3alpha1 External Filter api reference]: ../../../getambassador/v3alpha1/filter-external
+[ext_authz protocol]: ../../../../topics/running/services/ext-authz
+[FilterPolicy Resource]: ../filterpolicy
+[LinkerD]: https://linkerd.io/
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[DeniedHttpResponse]: https://github.com/envoyproxy/envoy/blob/1230c6cfba3791e4544b4ca23cacdbfc20a6fbaa/api/envoy/service/auth/v3/external_auth.proto
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md
new file mode 100644
index 000000000..34a4d7c07
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md
@@ -0,0 +1,174 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **JWT Filter** Type (v1alpha1)
+
+The `JWT Filter` performs JWT validation on a [bearer token][] present in the HTTP header. If the bearer token JWT doesn't validate,
+or has insufficient scope, an RFC 6750-complaint error response with a `www-authenticate` header is returned. The list of acceptable
+signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in the `Filter` configuration. Only RSA and `none`
+algorithms are supported.
+
+
+
+This doc is an overview of all the fields on the `JWT Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `JWT Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 JWT Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## JWT Filter API Reference
+
+To create a JWT Filter, the `spec.type` must be set to `jwt`, and the `jwt` field must contain the configuration for your
+JWT Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-jwt-filter"
+ namespace: "example-namespace"
+spec:
+ type: "jwt" # required
+ jwt: JWTFilter # required when `type: "jwt"`
+ jwksURI: string # optional, required unless `validAlgorithms: ["none"]`
+ validAlgorithms: []Enum # optional, default is all supported algos except for `"none"`
+ audience: string # optional (unless requireAudience: `true`)
+ requireAudience: bool # optional, default: `false`
+ issuer: string # optional (unless requireIssuer: `true`)
+ requireIssuer: bool # optional, default: `false`
+ requireExpiresAt: bool # optional, default: `false`
+ leewayForExpiresAt: Duration # optional
+ requireNotBefore: bool # optional, default: `false`
+ leewayForNotBefore: Duration # optoinal
+ requireIssuedAt: bool # optional, default: `false`
+ leewayForIssuedAt: Duration # optional
+ injectRequestHeaders: []AddHeaderTemplate # optional
+ - name: string # required
+ value: string (GoLang Template) # required
+ maxStale: Duration # optional
+ insecureTLS: bool # optional, default: `false`
+ renegotiateTLS: Enum # optional, default: `"never"`
+ errorResponse: CustomErrorResponse # optional
+ realm: string # optional, default is {name}.{namespace} of the JWT Filter
+ bodyTemplate: string (GoLang Template) # optional
+ headers: []AddHeaderTemplate # optional, max 16 items
+ - name: string # required
+ value: string (GoLang Template) # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### JWTFilter
+
+| **Field** | **Type** | **Description** |
+|------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+ `jwksURI` | `string` | A URI that returns the JWK Set per RFC 7517. This is required unless validAlgorithms=["none"], in that case verifying the signature of the token is disabled. This is considered unsafe and is discouraged when receiving tokens from untrusted sources. |
+ `validAlgorithms` | \[\][ValidAlgorithms][](`Enum`) | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+ `audience` | `string` | Identifies the recipient that the JWT is intended for and will be used to validate the provided token is intended for the configured audience. If not provided then `aud` claim on incoming token is not validated and will be considered valid. If `aud` is unset on the token by default it will be considered valid even if it doesn't match the audience value. To enforce that a token has the aud claim, then set `requireAudience: true`. |
+ `requireAudience` | `bool` | Modifies the validation behavior for when the audience claim (aud) is unset on the incoming token. `false` (default) => if aud claim is unset then claim is considered valid. `true` => if aud claim is unset then claim/token are invalid |
+ `issuer` | `string` | Identifies the expected AuthorizationServer that isssued the token. If not provided then the issuer claim will not be validated. If `issuer` is unset on the token by default it will be considered valid even if it doesn't match the expected issuer value. To enforce that a token has the issuer claim, then set `requireIssuer: true`. |
+ `requireIssuer` | `bool` | Modifies the validation behavior for when the issuer claim (iss) is unset on the incoming token. `false` (default) => if aud claim is unset on incoming token then claim is considered valid `true` => if exp claim is unset then claim is invalid |
+ `requireExpiresAt` | `bool` | Modifies the validation behavior for when the expiresAt claim (exp) is unset on the incoming token. `false` (default) => if exp claim is unset on incoming token then claim is valid `true` => if exp claim is unset then claim/token are invalid |
+ `leewayForExpiresAt` | [Duration][] | Allows token expired by this much to still be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `requireNotBefore` | `bool` | Modifies the validation behavior for when the not before time claim (nbf) is unset on the incoming token. `false` (default) => if `nbf` claim is unset on incoming token then claim is valid `true` => if `nbf` claim is unset then claim/token are invalid |
+ `leewayForNotBefore` | [Duration][] | Allows tokens that shouldn't be used until this much in the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `requireIssuedAt` | `bool` | Modifies the validation behavior for when the issuedAt claim (iat) is unset on the incoming token. `false` (default) => if `iat` claim is unset on incoming token then claim is valid `true` => if `iat` claim is unset then claim/token are invalid |
+ `leewayForIssuedAt` | [Duration][] | Allows tokens issued by this much into the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `injectRequestHeaders` | \[\][AddHeaderTemplate][] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream request headers as values. For example, attaching user email claim to a header from the token. |
+ `maxStale` | [Duration][] | Sets the duration that JWKs keys and OIDC discovery responses will be cached, ignoring any caching headers when configured |
+ `insecureTLS` | `bool` | Disables TLS verification for cases when jwksURI begins with `https://`. |
+ `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the JWTFilter will renegotiateTLS with the `jwksURI` server and if so what supported method of renegotiation will be used. |
+ `errorResponse` | [CustomErrorResponse][] | Allows setting a custom Response to the downstream client when an invalid JWT is received. |
+
+### Duration
+
+**Appears On**: [JWTFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ValidAlgorithms
+
+**Appears On**: [JWTFilter][]
+The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not
+in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP,
+as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided
+to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected.
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+
+### AddHeaderTemplate
+
+**Appears On**: [JWTFilter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream
+request headers as values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+`value` is the value of the header to set and is evaluated as a special GoLang Template.
+This allows the header value to be set based on the JWT value. The value is specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.token.Raw` → The raw JWT (`string`)
+- `.token.Header` → The JWT header, as parsed JSON (`map[string]interface{}`)
+- `.token.Claims` → The JWT claims, as parsed JSON (`map[string]interface{}`)
+- `.token.Signature` → The token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+Also available to the template are [the standard functions available to Go text/templates][], as well as:
+
+- a `hasKey` function that takes the a string-indexed map as arg1, and returns whether it contains the key arg2. (This is the same as [the Sprig function of the same name][].)
+- a `doNotSet` function that causes the result of the template to be discarded, and the header field to not be adjusted. This is useful for only conditionally setting a header field; rather than setting it to an empty string or `""`. Note that this does not unset an existing header field of the same name and could be a potential security vulnerability depending on how this is used if an untrusted client spoofs these headers.
+
+
+ Any headers listed will override (not append to) the original request header with that name.
+
+
+### CustomErrorResponse
+
+**Appears On**: [JWTFilter][]
+Allows setting a custom Response to the downstream client when an invalid JWT is received.
+
+| **Field** | **Type** | **Description** |
+|------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `realm` | `string` | Indicates the scope of protection or the application that is checking the token. By default, this is set to the fully qualified name of the `JWT Filter` as `"{name}.{namespace}"` to identify which filter rejected the error. This can be overriden to provide more relevant information to end-users. |
+| `bodyTemplate` | `string` (GoLang Template) | Golang `text/template` string that will be evaluated and used to build the format returned. |
+| `headers` | \[\][AddHeaderTemplate][] | Allows providing additional http response headers for the error response. The current maximum is 16 headers, which aligns with the Gateway-API and modified headers on HTTPRoutes. |
+
+`bodyTemplate` specifies body of the error response returned to the downstream client; specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.status_code` → The HTTP status code to be returned (`int`)
+- `.httpStatus` → An alias for .status_code (`int`, hidden from `{{ . | json "" }}`)
+- `.message` → The error message (`string`)
+- `.error` → The raw Go error object that generated .message (`error`, hidden from `{{ . | json "" }}`)
+- `.error.ValidationError` → The JWT validation error, will be nil if the error is not purely JWT validation (`jwt.ValidationError` insufficient scope, malformed or missing Authorization header)
+- `.request_id` → The Envoy request ID, for correlation (`string`, hidden from `{{ . | json "" }}` unless `.status_code` is in the `5xx` range)
+- `.requestId` → An alias for .request_id (`string`, hidden from `{{ . | json "" }}`)
+
+Also availabe to the template are [the standard functions available to Go text/templates][], as well as:
+
+- A `json` function that formats arg2 as JSON, using the arg1 string as the starting indentation. For example, the template `{{ json "indent>" "value" }}` would yield the string `indent>"value"`.
+
+[JWTFilter]: #jwtfilter
+[ValidAlgorithms]: #validalgorithms
+[AddHeaderTemplate]: #addheadertemplate
+[CustomErrorResponse]: #customerrorresponse
+[Duration]: #duration
+[the v3alpha1 JWT Filter api reference]: ../../../getambassador/v3alpha1/filter-jwt
+[bearer token]: https://datatracker.ietf.org/doc/html/rfc6750
+[Go text/template string]: https://pkg.go.dev/text/template
+[the standard functions available to Go text/templates]: https://pkg.go.dev/text/template#hdr-Functions
+[the Sprig function of the same name]: https://masterminds.github.io/sprig/dicts.html#haskey
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md
new file mode 100644
index 000000000..26ba7d496
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md
@@ -0,0 +1,341 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **OAuth2 Filter** Type (v1alpha1)
+
+The OAuth2 Filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+
+
+
+This doc is an overview of all the fields on the `OAuth2 Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `OAuth2 Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 OAuth2 Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## OAuth2 Filter API Reference
+
+To create an OAuth2 Filter, the `spec.type` must be set to `oauth2`, and the `oauth2` field must contain the configuration for your
+OAuth2 filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-oauth2-filter"
+ namespace: "example-namespace"
+spec:
+ type: "oauth2" # required
+ oauth2: OAuth2Filter # required when `type: "oauth2"`
+ authorizationURL: string # required, must be an absolute url
+ expirationSafetyMargin: Duration # optional
+ injectRequestHeaders: []AddHeaderTemplate # optional
+ - name: string # required
+ value: string (GoLang Template) # required
+ allowMalformedAccessToken: bool # optional, default: `false`
+ accessTokenValidation: Enum # optional, default: `"auto"`
+ accessTokenJWTFilter: JWTFilterReference # optional
+ name: string # required
+ namespace: string # optional
+ inheritScopeArgument: bool # optional, default: `false`
+ stripInheritedScope: bool # optional, default: `false`
+ arguments: JWTArguments # optional
+ scope: []string # optional
+ clientAuthentication: ClientAuthentication # optional
+ method: Enum # optional, default: `"HeaderPassword"`
+ jwtAssertion: JWTAssertion # optional
+ setClientID: bool # optional, default: `false`
+ audience: string # optional
+ signingMethod: Enum # optional, default: `RS256`
+ lifetime: Duration # optional, default: `"1m`
+ setNBF: bool # optional, default: `false`
+ nbfSafetyMargin: Duration # optional
+ setIAT: bool # optional, default: `false`
+ otherClaims: []byte # optional, default: `{}`
+ otherHeaderParameters: []byte # optional, default: `{}`
+ grantType: Enum # required
+ authorizationCodeSettings: AuthorizationCodeSettings # optional, used when `grantType: "AuthorizationCode"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+ name: string # optional
+ namespace: string # optional
+ maxStale: Duration # optional
+ insecureTLS: bool # optional, default: `false`
+ renegotiateTLS: Enum # optional, default: `"never"`
+ protectedOrigins: []Origin # required, min items: 1, max items: 16
+ - origin: string # required, must be an absolute URL, max length: 255
+ includeSubdomains: bool # optional, defualt: `false`
+ allowedInternalOrigins: []string # optional, max items: 16
+ resourceOwnerSettings: ResourceOwnerSettings # optoinal, used when `grantType: "ResourceOwnder"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+ passwordSettings: PasswordSettings # optional, used when `grantType: "Password"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### OAuth2Filter
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `authorizationURL` | `string` | Identity Provider Issuer URL which hosts the OpenID provider well-known configurartion. The URL must be an absolute URL. Per [OpenID Connect Discovery 1.0][] the configuration must be provided in a json document at the path `/.well-known/openid-configuration`. This is used by the OAuth2 Filter for determining things like the AuthorizationEndpoint, TokenEndpoint, JWKs endpint, etc... |
+| `expirationSafetyMargin` | [Duration][] | Sets a buffer to check if the Token is expired or is going to expire within the safety margin. This is to ensure the application has enough time to reauthenticate to adjust for clock skew and network latency. By default, no safety margin is added. If a token is received with an expiration less than this field, then the token is considered to already be expired. |
+| `injectRequestHeaders` | \[\][][AddHeaderTemplate] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token. |
+| `allowMalformedAccessToken` | `bool` | Allows any access token even if they are not RFC 6750-compliant. |
+| `accessTokenValidation` | `Enum`(`"auto"`,`"jwt"`,`"userinfo"`) | Sets the method used for validating an AccessToken. |
+| `accessTokenJWTFilter` | [JWTFilterReference][] | Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes |
+| `clientAuthentication` | [ClientAuthentication][] | Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password. |
+| `grantType` | `Enum`(`"AuthorizationCode"`,`"ClientCredentials"`,`"Password"`,`"ResourceOwner"`) | Sets the Authorization Flow that the filter will use to authenticate the incoming request. |
+| `authorizationCodeSettings` | [AuthorizationCodeSettings][] | Specific settings that configure the `AuthorizationCode` grant type. |
+| `resourceOwnerSettings` | [ResourceOwnerSettings][] | Specific settings that configure the `ResourceOwner` grant type. |
+| `passwordSettings` | [PasswordSettings][] | Specific settings that configure the `Password` grant type. |
+
+**`grantType` options**:
+
+- `"AuthorizationCode"`: Authenticate by redirecting to a login page served by the identity provider.
+- `"Password"`: Authenticate by requiring `X-Ambassador-Username` and `X-Ambassador-Password` on all incoming requests, and use them to authenticate with the identity provider using the OAuth2 Resource Owner Password Credentials grant type.
+- `"ClientCredentials"`: Authenticate by requiring that the incoming HTTP request include as headers the credentials for Ambassador to use to authenticate to the identity provider.
+ - The type of credentials needing to be submitted depends on the `clientAuthentication.method` (below):
+ - For `"HeaderPassword"` and `"BodyPassword"`, the headers `X-Ambassador-Client-ID` and `X-Ambassador-Client-Secret` must be set.
+ - For `"JWTAssertion"`, the `X-Ambassador-Client-Assertion` header must be set to a JWT that is signed by your client secret, and conforms with the requirements in RFC 7521 section 5.2 and RFC 7523 section 3, as well as any additional specified by your identity provider.
+
+**`accessTokenValidation` options**:
+
+- `"jwt"`: Validates the Access Token as a JWT.
+
+ - By default: It accepts the RS256, RS384, or RS512 signature algorithms, and validates the signature against the JWKS from
+OIDC Discovery. It then validates the `exp`, `iat`, `nbf`, `iss` (with the Issuer from OIDC Discovery), and `scope` claims: if present,
+none of the scope values are required to be present. This relies on the identity provider using non-encrypted signed JWTs as
+Access Tokens, and configuring the signing appropriately
+ - This behavior can be modified by delegating to [JWT Filter][] with `accessTokenJWTFilter`:
+
+- `"userinfo"`: Validates the access token by polling the OIDC UserInfo Endpoint. This means that $productName$ must initiate
+an HTTP request to the identity provider for each authorized request to a protected resource. This performs poorly,
+but functions properly with a wider range of identity providers. It is not valid to set `accessTokenJWTFilter` if
+`accessTokenValidation`: `userinfo`.
+
+- `"auto"` attempts to do `"jwt"` validation if any of these conditions are true:
+ - `accessTokenJWTFilter` is set
+ - `grantType` is `"ClientCredentials"`
+ - the Access Token parses as a JWT and the signature is valid,
+ - If none of the above conditions are satisfied, it falls back to `"userinfo"` validation.
+
+### Duration
+
+**Appears on**: [Oauth2Filter][], [JWTAssertion][], [AuthorizationCodeSettings][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### AddHeaderTemplate
+
+**Appears On**: [OAuth2Filter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+The header value can be set based on the JWT value. If an `OAuth2 Filter` is chained with a [JWT filter][] with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the `OAuth2 Filter` will populate the value. The value is specified as a [Go text/template][] string, with the following data made available to it:
+
+- `.token.Raw` → The access token raw JWT (`string`)
+- `.token.Header` → The access token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.token.Claims` → The access token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.token.Signature` → The access token signature (`string`)
+- `.idToken.Raw` → The raw id token JWT (`string`)
+- `.idToken.Header` → The id token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Claims` → The id token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Signature` → The id token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+### JWTFilterReference
+
+**Appears On**: [OAuth2Filter][]
+Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes
+
+| **Field** | **Type** | **Description** |
+|------------------------|-------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `namespace` | `string` | Namespace of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `inheritScopeArgument`:| `bool` | Will use the same scope as set on the FilterPolicy OAuth2Arguments. If the JWTFilter sets a scope as well then the union of the two will be used. |
+| `stripInheritedScope` | `bool` | Determines whether or not to santized a scope that is formatted as an URI and was inherited from the FilterPolicy OAuth2Arguments. This will be done prior to passing it along to the referenced JWTFilter. This requires that InheritScopeArgument is true. |
+| `arguments` | [JWTArguments][] | Defines the input arguments that can be set for a JWTFilter. |
+
+### JWTArguments
+
+**Appears On**: [JWTFilterReference][]
+Defines the input arguments that can be set for a JWTFilter.
+
+| **Field** | **Type** | **Description** |
+|------------|-------------|---------------------------------------------------------------------------------------------------------|
+| `scope` | `[]string` | A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope. |
+
+**Some notes about `scope`**:
+
+- If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed.
+- If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+- As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+- The ordering of scope values does not matter, and is ignored.
+
+
+### ClientAuthentication
+
+**Appears On**: [OAuth2Filter][]
+Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password.
+
+| **Field** | **Type** | **Description** |
+|----------------|----------------------------------|---------------------------------------------------------------------------------------------------------|
+| `method` | `Enum`(`"HeaderPassword"`,`"BodyPassword"`,`"JWTAssertion"`) | Defines the type of client authentication that will be used |
+| `jwtAssertion` | [JWTAssertion][] | This field is only used when `method: "JWTAssertion"`. Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow. |
+
+`method` options:
+
+- `"HeaderPassword"`: Treat the client secret as a password, and pack that in to an HTTP header for HTTP Basic authentication.
+- `"BodyPassword"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+- `"JWTAssertion"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+
+### JWTAssertion
+
+**Appears On**: [ClientAuthentication][]
+Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|-------------------------|---------------------------------------------------------------------------------------------------------|
+| `setClientID` | `bool` | Whether to set the Client ID as an HTTP parameter; setting it as an HTTP parameter is optional (per RFC 7521 §4.2) because the Client ID is also contained in the JWT itself, but some identity providers document that they require it to also be set as an HTTP parameter anyway. |
+| `audience` | `string` | This field is ignored when `grantType: "ClientCredentials"`. The audience your IDP requires for authentication. If not set then the default will be to use the token endpoint from the OIDC discovery document. |
+| `signingMethod` | [ValidAlgorithms][] | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `lifetime` | [Duration][] | This field is ignored when `grantType: "ClientCredentials"`. The lifetime of the generated JWT; just enough time for the request to the identity provider to complete (plus possibly an extra allowance for clock skew). |
+| `setNBF` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "nbf" ("Not Before") claim in the generated JWT. |
+| `nbfSafetyMargin` | [Duration][] | This field is only used when `setNBF: true` The safety margin to build-in to the "nbf" claim, to allow for clock skew between ambassador and the identity provider. |
+| `setIAT` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "iat" ("Issued At") claim in the generated JWT. |
+| `otherClaims` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Key/value pairs that will be add to the JWT sent for client Auth to the Identity Provider |
+| `otherHeaderParameters` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Any extra JWT header parameters to include in the generated JWT non-standard claims to include in the generated JWT; only the "typ" and "alg" header parameters are set by default. |
+
+### ValidAlgorithms
+
+**Appears On**: [JWTAssertion][]
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+ - The secret must be a PEM-encoded Eliptic Curve private key
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+ - The secret is a raw string of bytes; it can contain anything
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+ - The secret must be a PEM-encoded RSA private key
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+ - The secret must be a PEM-encoded RSA private key
+
+### AuthorizationCodeSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `AuthorizationCode` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. The Kubernetes Secret must of the `generic` type, with the value stored under the key `oauth2-client-secret` |
+| `maxStale` | [Duration][] | How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also ignores the `no-store` and `no-cache` Cache-Control directives on responses. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Note that if you MUST set `maxStale` as a consistent value on each `Filter` resource to get predictable caching behavior. |
+| `insecureTLS` | `bool` | Tells the $productName$ to skip verifying the IdentityProvider server when communicating with the various endpoints. This is typically needed when using an IdentityProvider configured with self-signed certs. |
+| `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the OAuth2 Filter will renegotiateTLS with the iDP server and if so what supported method of renegotiation will be used. |
+| `protectedOrigins` | \[\][Origin][] | This field is only used when `grantType: "AuthorizationCode"`. List of origins (domains) that the OAuth2 Filter is configured to protect. Setting multiple origins allows for protecting multiple domains using the same Session and Token that is retrieved from the Identity Provider. When setting multiple protected origins, the first origin will be used for the final redirect to the IdentityProvider therefore the identity provider needs to be configured to allow redirects from that origin. However, it is recommended that all protected origins are registered with the IdentityProvider because this is subject to change in the future. Only the scheme `(https://)` and authority `(example.com:1234)` parts are used; the path part of the URL is ignored. You will need to register each origin in `protectedOrigins` as an authorized callback endpoint with your identity provider. The URL will look like `{{ORIGIN}}/.ambassador/oauth2/redirection-endpoint`. |
+
+> **Note**: If you provide more than one protectedOrigin, all share the same authentication system, so that logging into one origin logs you into all origins; to have multiple domains that have separate logins, use separate `Filters`.
+
+### SecretReference
+
+**Appears On**: [AuthorizationCodeSettings][], [PasswordSettings][], [ResourceOwnerSettings][]
+A reference to a [Kubernetes Secret][].
+
+| **Field** | **Type** | **Description** |
+|-------------|------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the Kubernetes Secret being referenced. |
+| `namespace` | `string` | Namespace of the Kubernetes Secret being referenced. |
+
+### Origin
+
+**Appears On**: [AuthorizationCodeSettings][]
+A domain that the OAuth2 Filter is configured to protect. It is recommended that all protected origins are registered with the IdentityProvider because this is subject to change in the future.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|------------|---------------------------------------------------------------------------------------------------------|
+| `origin` | `string` | The absolute URL (schema://hostname) that is protected by the OAuth2 Filter |
+| `includeSubdomains` | `bool` | Enables protecting sub-domains of the domain identified in the Origin field. Example, when `Origin=https://example.com` then the subdomain of `https://app.example.com` would be watched. |
+| `allowedInternalOrigins` | `[]string` | Indentifies a list of allowed internal origins that were set by a downstream proxy via a host header rewrite. The origins identified in this list ensures the request is allowed and will ensure it redirects correctly to the upstream origin. For example, a downstream client will communicate with an origin of `https://example.com` but then an internal proxy will do a rewrite so that the host header received by Edge Stack is `http://example.internal`. |
+
+**Note about `allowedInternalOrigins`**: This field is primarily used to allow you to tell $productName$ that there is another gateway
+in front of $productName$ that rewrites the Host header, so that on the internal network between that gateway and $productName$, the
+origin appears to be `allowedInternalOrigins` instead of `origin`. As a special-case the scheme and/or authority of the `allowedInternalOrigins`
+may be `"*"`, which matches any scheme or any domain respectively.
+Using `"*"` is most useful in configurations with exactly one protected origin; in such a configuration, $productName$ doesn't need
+to know what the origin looks like on the internal network, just that a gateway in front of $productName$ is rewriting it.
+It is invalid to use `"*"` with `includeSubdomains: true`.
+
+For example, if you have a gateway in front of $productName$ handling traffic for `myservice.example.com`, terminating TLS and routing
+that traffic to Ambassador with the name `ambassador.internal`, you might write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - http://ambassador.internal
+```
+
+or, to avoid being fragile to renaming ambassador.internal to something else, since there are not multiple origins that the `Filter` must
+distinguish between, you could instead write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - "*://*"
+```
+
+### ResourceOwnerSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `ResourceOwner` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. |
+
+### PasswordSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `Password` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. |
+
+[AddHeaderTemplate]: #addheadertemplate
+[Oauth2Filter]: #oauth2filter
+[JWTFilterReference]: #jwtfilterreference
+[ClientAuthentication]: #jwtfilterreference
+[AuthorizationCodeSettings]: #authorizationcodesettings
+[ResourceOwnerSettings]: #resourceownersettings
+[PasswordSettings]: #passwordsettings
+[JWTArguments]: #jwtarguments
+[JWTAssertion]: #jwtassertion
+[ValidAlgorithms]: #validalgorithms
+[SecretReference]: #secretreference
+[Origin]: #origin
+[Duration]: #duration
+[JWT Filter]: ../filter-jwt
+[the v3alpha1 OAuth2 Filter api reference]: ../../../getambassador/v3alpha1/filter-oauth2
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
+[Kubernetes secret]: https://kubernetes.io/docs/concepts/configuration/secret/
+[OpenID Connect Discovery 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md
new file mode 100644
index 000000000..7dddf64ff
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md
@@ -0,0 +1,42 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Plugin Filter** Type (v1alpha1)
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled into a .so file,
+which you load into the Envoy Proxy container at `/etc/ambassador-plugins/${NAME}.so`. For more information about how requests are
+matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `Plugin Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 Plugin Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## Plugin Filter API Reference
+
+To create a Plugin Filter, the `spec.type` must be set to `plugin`, and the `plugin` field must contain the configuration for your Plugin Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-plugin-filter"
+ namespace: "example-namespace"
+spec:
+ type: "plugin" # required
+ plugin: PluginFilter # required when `type: "plugin"`
+ name: string # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+`name`: Indicates the compiled binaries name excluding the extension for the Plugin.
+Envoy Proxy will look for the .so file in the `/etc/ambassador-plugins` directory.
+For example, if `name: "example-plugin"` the .so file should be available at
+`"/etc/ambassador-plugins/example-plugin.so"` on the Envoy Proxy container.
+
+[FilterPolicy Resource]: ../filterpolicy
+[the v3alpha1 Plugin Filter api reference]: ../../../getambassador/v3alpha1/filter-plugin
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter.md
new file mode 100644
index 000000000..91ff36574
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filter.md
@@ -0,0 +1,78 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **Filter** Resource (v1alpha1)
+
+The `Filter` custom resource works in conjunction with the [FilterPolicy custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending them to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests, such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute against those requests. Filters are largely used to add built-in authentication and security, but
+$productName$ also supports developing custom filters to add your own processing and logic.
+
+
+
+This doc is an overview of all the fields on the `Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## Filter API Reference
+
+Filtering is configured using `Filter` custom resources. The body of the resource `spec` depends on the filter type:
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-filter"
+ namespace: "example-namespace"
+spec:
+ type: Enum # required
+ jwt: JWTFilter # optional, required when `type: "jwt"`
+ oauth2: OAuth2Filter # optional, required when `type: "oauth2"`
+ apikey: APIKeyFilter # optional, required when `type: "apikey"`
+ external: ExternalFilter # optional, required when `type: "external"`
+ plugin: PluginFilter # optional, required when `type: "plugin"`
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### FilterSpec
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum` (`"jwt"`/`"oauth2"`/`"apikey"`/`"external"`/`"plugin"`) | Required field that identifies the type of the Filter that is configured to be executed on a request. |
+| `jwt` | [JWTFilter][] | Provides configuration for the JWT Filter type |
+| `oauth2` | [OAuth2Filter][] | Provides configuration for the OAuth2 Filter type |
+| `apikey` | [APIKeyFilter][] | Provides configuration for the APIKey Filter type |
+| `external` | [ExternalFilter][] | Provides configuration for the External Filter type |
+| `plugin` | [PluginFilter][] | Provides configuration for the Plugin Filter type |
+
+### FilterStatus
+
+This field is set automatically by $productName$ to provide info about the status of the `Filter`.
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-------------------------------------------------------------------------------------------------------------------|
+| `conditions` | \[\][metav1.Condition][] | Describes the current conditions of the WebApplicationFirewall, known conditions are `Accepted`;`Ready`;`Rejected` |
+
+
+ The short name for Filter is fil, so you can get filters using kubectl get filter or kubectl get fil.
+
+
+[FilterPolicy custom resource]: ../filterpolicy
+[JWTFilter]: ../filter-jwt
+[PluginFilter]: ../filter-plugin
+[OAuth2Filter]: ../filter-oauth2
+[APIKeyFilter]: ../filter-apikey
+[ExternalFilter]: ../filter-external
+[the v3alpha1 Filter api reference]: ../../../getambassador/v3alpha1/filter
+[metav1.Condition]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md
new file mode 100644
index 000000000..07caacdd4
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md
@@ -0,0 +1,183 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **FilterPolicy** Resource (v1alpha1)
+
+The `FilterPolicy` custom resource works in conjunction with the [Filter custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute on those requests.
+
+
+
+This doc is an overview of all the fields on the `FilterPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `FilterPolicy` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 FilterPolicy api reference][].
+
+
+ v1alpha1FilterPolicies can only be reference v1alpha1Filters.
+
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## FilterPolicy API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec: FilterPolicy
+ rules: []FilterPolicyRule # required, min items: 1
+ - host: string # optional, default: `"*"`
+ path: string # optional, default: `"*"`
+ precedence: int # optional
+ filterRefs: []FilterReference # optional, max items: 5
+ - name: string # required
+ namespace: string # optional
+ onDeny: Enum # optional, default: `"break"`
+ onAllow: Enum # optional, default: `"continue"`
+ ifRequestHeader: HTTPHeaderMatch # optional
+ type: Enum # optional, default: `"Exact"`
+ name: string # required
+ value: string # optional, max length: 4096
+ negate: bool # optional, default: `false`
+ arguments: FilterArguments # optional
+ type: Enum # required
+ jwt: JWTArguments # optional, required when `type: "jwt"`
+ scope: []string # optional
+ oauth2: OAuth2Arguments # optional, required when `type: "oauth2"`
+ scope: []string # optional
+ insteadOfRedirect: OAuth2Redirect # optional
+ statusCode: int # optional
+ ifRequestHeader: HTTPHeaderMatch # optional
+ filterRefs: []FilterReference # optional
+ sameSite: Enum # optional
+status: FilterPolicyStatus # field managed by controller
+ conditions: []metav1.Condition # max items: 8
+ rules: []FilterPolicyRuleStatus # max items: 64
+ - index: string
+ host: string
+ path: string
+ conditions: []metav1.Condition
+```
+
+### FilterPolicy
+
+| **Field** | **Type** | **Description** |
+|------------|----------------------------|-----------------------------------------------------------------------------------|
+| `rules` | \[\][FilterPolicyRule][] | Set of matching rules that are checked against incoming request to determine which set of Filter's to apply. If no matches are found then the request is allowed through to the upstream service without executing any Filters. |
+
+### FilterPolicyRule
+
+**Appears on**: [FilterPolicy][]
+Configures matching rules that are checked against incoming request to determine which `Filter` to apply (if any).
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-----------------------------------------------------------------------------------|
+| `host` | `string` | "glob-string" that matches on the `:authority` header of the incoming request. If not set it will match on all incoming requests. |
+| `path` | `string` | "glob-string" that matches on the request path. If not provided then it will match on all incoming requests. |
+| `filterRefs` | \[\][FilterReference][] | List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths. |
+| `precedence` | `int` | Allows forcing a precedence ordering on the rules. By default the rules are evaluated in the order they are in the `FilterPolicy.spec.rules` field. However, multiple FilterPolicy's can be applied to a cluster. To ensure that a specific ordering is enforced then using a precedence is an option. |
+
+### FilterReference
+
+**Appears on**: [FilterPolicyRule][], [OAuth2Redirect][]
+List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths.
+
+| **Field** | **Type** | **Description** |
+|-------------------|-----------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name that identifies the Filter |
+| `namespace` | `string` | Kubernetes namespace that the Filter resides. It must be a RFC 1123 label. Valid values include: `"example"`, Invalid values include: `"example.com"` (`.` is an invalid character). This validation is based off of the [corresponding Kubernetes validation]. |
+| `onDeny` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `onAllow` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not. |
+| `arguments` | [FilterArguments][] | Strongly typed input arguments that can be passed into a Filter on per [FilterReference][] level allowing for different behavior on different Rules. |
+
+### HTTPHeaderMatch
+
+**Appears on**: [FilterPolicyRule][], [OAuth2Redirect][]
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum`(`"Exact"`,`"RegularExpression"`) | The semantics of how HTTP header values should be evaluated |
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of HTTP Header to be matched. If type is RegularExpression then this must be a valid regex with length being at least 1. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### FilterArguments
+
+**Appears on**: [FilterPolicyRule][]
+Strongly typed input arguments that can be passed into a Filter on per [FilterReference][] level allowing for different behavior on different Rules.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum` (`"jwt"`/`"oauth2"`) | Identifies the expected type of the arguments that will be passed to the `FilterRef`. This must match the type of the `filterRef` and if it doesn't the `FilterPolicy` `rule` will be considered invalid and a status condition will be updated to indicate the mismatch. |
+| `jwt` | [JWTArguments][] | Defines the input arguments that can be set for an [JWT Filter][] on a per `FilterPolicy` `rule` level. |
+| `oauth2` | [OAuth2Arguments][] | Defines the input arguments that can be set for an [OAuth2 Filter][] on a per `FilterPolicy` `rule` level. |
+
+### JWTArguments
+
+**Appears on**: [FilterArguments][]
+Defines the input arguments that can be set for an [JWT Filter][] on a per `FilterPolicy` `rule` level.
+
+| **Field** | **Type** | **Description** |
+|--------------|-------------|-----------------------------------------------------------------------------------|
+| `scope` | `[]string` | Set of scopes the JWT will be validated against |
+
+### OAuth2Arguments
+
+**Appears on**: [FilterArguments][]
+Defines the input arguments that can be set for an [OAuth2 Filter][] on a per `FilterPolicy` `rule` level.
+
+| **Field** | **Type** | **Description** |
+|---------------------|-------------------------------------------------|-----------------------------------------------------------------------------------|
+| `scope` | `[]string` | Set of scopes the JWT will be validated against |
+| `insteadOfRedirect` | [OAuth2Redirect][] | Allows customizing the behavior of the OAuth2 redirect and whether it will redirect the browser or not. |
+| `sameSite` | `Enum`(`"default"`,`"none"`,`"lax"`,`"strict"`) | Set of options for setting the SameSite attribute on a cookie. [https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00][] for details. |
+
+### OAuth2Redirect
+
+**Appears on**: [OAuth2Arguments][]
+Allows customizing the behavior of the OAuth2 redirect and whether it will redirect the browser or not.
+
+| **Field** | **Type** | **Description** |
+|-------------------|-------------------------|-----------------------------------------------------------------------------------|
+| `statusCode` | `int` | The HTTP status code to be used in response. If filterRef is not set then this will default to a 403 forbidden. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Allows only applying the InsteadOfRedirect logic when a the header matches. |
+| `filterRefs` | \[\][FilterReference][] | List of references to Filter's that will be applied when an OAuth2 session has expired and the user would like to try a secondary authentication mechanism without redircting to the iDP. Nesting an [OAuth2 Filter][] inside of an [OAuth2 Filter][] is not supported. |
+
+### FilterPolicyStatus
+
+Automatically managed by the controller to reflect the state of the `FilterPolicy`
+
+| **Field** | **Type** | **Description** |
+|------------------------|---------------------------|-----------------------------------------------------------------------------------|
+| `conditions` | \[\][metav1.Condition][] | Describes the current condition of the `FilterPolicy`. |
+| `rules` |`[]FilterPolicyRuleStatus` | Describes the status for each unique Rule defined in the `Spec` |
+| `rules.index` | `string` | The zero-based index of the rule within `rules` with the problem to help with identifying the error |
+| `rules.host` | `string` | `host` of the rule with the problem to help with identifying the error
+| `rules.path` | `string` | `path` of the rule with the problem to help with identifying the error |
+| `rules.conditions` | \[\][metav1.Condition][] | Describes the current condition of a specific `rule`. |
+
+[FilterPolicy]: #filterpolicy
+[FilterPolicyRule]: #filterpolicyrule
+[FilterReference]: #filterreference
+[FilterArguments]: #filterarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[OAuth2Redirect]: #oauth2redirect
+[OAuth2Arguments]: #oauth2arguments
+[JWTArguments]: #jwtarguments
+[JWT Filter]: ../filter-jwt
+[OAuth2 Filter]: ../filter-oauth2
+[Filter custom resource]: ../filter
+[the v3alpha1 FilterPolicy api reference]: ../../../getambassador/v3alpha1/filterpolicy
+[corresponding Kubernetes validation]: https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
+[https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00]: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
+[metav1.Condition]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md
new file mode 100644
index 000000000..d2fcf7f35
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md
@@ -0,0 +1,84 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **WebApplicationFirewall** Resource (v1alpha1)
+
+The `WebApplicationFirewall` provides the configuration for an instance of a Web Application Firewall, and the
+[WebApplicationFirewallPolicy][] resource configures the matching patterns for when `WebApplicationFirewalls` get executed against requests.
+
+This doc is an overview of all the fields on the `WebApplicationFirewall` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+Tutorials and guides for Web Application Firewalls can be found in the [usage guides section][]
+
+
+ The WebApplicationFirewall resource was introduced more recently than the Filter and FilterPolicy resources, and does not have an older getambassador.io/v3alpha1 CRD version
+
+
+## WebApplicationFirewall API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewall
+metadata:
+ name: "example-waf"
+ namespace: "example-namespace"
+spec:
+ firewallRules: FirewallRules # required, One of configMapRef;file;http must be set below
+ sourceType: Enum # required
+ configMapRef: ConfigMapReference # optional
+ name: string # required
+ namespace: string # required
+ key: string # required
+ file: string # optional
+ http: # optional
+ url: string # required, must be a valid URL.
+ logging: # optional
+ onInterrupt: # required
+ enabled: bool # required
+status: # field managed by controller
+ conditions: []metav1.Condition
+```
+
+### WebApplicationFirewall
+
+| **Field** | **Type** | **Description** |
+|--------------------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `firewallRules` | [FirewallRules][] | Defines the rules to be used for the Web Application Firewall |
+| `logging.onInterrupt.enabled` | `bool` | When enabled, creates additional log lines in the $productName$ pods whenever the `WebApplicationFirewall` interrupts a request. This is in addition to the logging config that is available via the firewall configuration files. |
+
+### FirewallRules
+
+Defines the rules to be used for the Web Application Firewall
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `sourceType` | `Enum`(`"file"`,`"configmap"`,`"http"`) | Identifies which method is being used to load the firewall rules. Value must be one of `configMapRef`;`file`;`http`. The value corresponds to the following fields for configuring the selected method. |
+| `configMapRef` | [ConfigMapReference][] | Defines a reference to a [Kubernetes ConfigMap][] to load firewall rules from. |
+| `file` | `string` | Location of a file on disk to load the firewall rules from. Example: `"/ambassador/firewall/waf.conf"`. Files can be mounted to the $productName$ auth service deployment pods using a `ConfigMap`, or similar approach. |
+| `http.url` | `string` | URL to fetch firewall rules from. If the rules are unable to be downloaded/parsed from the provided url for whatever reason, the requests matched to this `WebApplicationFirewall` will be allowed/denied based on the configuration of the `onError` field. |
+
+### ConfigMapReference
+
+Defines a reference to a [Kubernetes ConfigMap][] to load firewall rules from.
+
+| **Field** | **Type** | **Description** |
+|--------------|------------|-----------------------------------------------|
+| `name` | `string` | Name of the referenced Kuberntes `ConfigMap`. |
+| `namespace` | `string` | Namespace of the referenced Kuberntes `ConfigMap`.|
+| `key` | `string` | The key in the referenced Kuberntes `ConfigMap` to pull the rules data from. |
+
+## Web Application Firewall Usage Guides
+
+The following guides will help you get started using Web Application Firewalls
+
+- [Using Web Application Firewalls][] - Get started using `WebApplicationFirealls` quickly
+- [Rules for Web Application Firewalls][] - Info about creating and configuring firewall rules
+- [Web Application Firewalls in Production][] - Recommendations and info for creating and running `WebApplicationFirewalls` in a production environment
+
+[FirewallRules]: #firewallrules
+[ConfigMapReference]: #configmapreference
+[usage guides section]: #web-application-firewall-usage-guides
+[WebApplicationFirewallPolicy]: ../webapplicationfirewallpolicy
+[Using Web Application Firewalls]: ../../../../howtos/web-application-firewalls
+[Rules for Web Application Firewalls]: ../../../../howtos/web-application-firewalls-config
+[Web Application Firewalls in Production]: ../../../../howtos/web-application-firewalls-in-production
+[Kubernetes ConfigMap]: https://kubernetes.io/docs/concepts/configuration/configmap/
diff --git a/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md
new file mode 100644
index 000000000..f9a8a7fcd
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md
@@ -0,0 +1,102 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **WebApplicationFirewallPolicy** Resource (v1alpha1)
+
+The `WebApplicationFirewallPolicy` resource configures the matching patterns for when [WebApplicationFirewalls][] get executed against requests; while the
+`WebApplicationFirewall` resource provides the configuration for an instance of a Web Application Firewall.
+
+This doc is an overview of all the fields on the `WebApplicationFirewallPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+Tutorials and guides for Web Application Firewalls can be found in the [usage guides section][]
+
+
+ The WebApplicationFirewallPolicy resource was introduced more recently than the Filter and FilterPolicy resources, and does not have an older getambassador.io/v3alpha1 CRD version
+
+
+## WebApplicationFirewallPolicy API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewallPolicy
+metadata:
+ name: "example-wafpolicy"
+ namespace: "example-namespace"
+spec:
+ rules: []WafMatchingRule # required
+ - host: string # optional, default: `"*"` (runs on all hosts)
+ path: string # optional, default: `"*"` (runs on all paths)
+ ifRequestHeader: HTTPHeaderMatch # optional
+ type: Enum # optional, default: `"Exact"`
+ name: string # required
+ value: string # optional
+ negate: bool # optional, default: `false`
+ wafRef: # required
+ name: string # required
+ namespace: string # required
+ onError: # optional
+ statusCode: int # required, min: `400`, max: `599`
+ precedence: int # optional
+status: # field managed by controller
+ conditions: []metav1.Condition
+ ruleStatuses:
+ - index: int
+ host: string
+ path: string
+ conditions: []metav1.Condition
+```
+
+### WebApplicationFirewallPolicy Spec
+
+| **Field** | **Type** | **Description** |
+|-----------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `rules` | \[\][WafMatchingRule][] | This object configures matching requests and executes WebApplicationFirewalls on them. Multiple different rules can be supplied in one `WebApplicationFirewallPolicy` instead of multiple separate `WebApplicationFirewallPolicy` resouurces if desired. |
+
+### WafMatchingRule
+
+| **Field** | **Type** | **Description** |
+|----------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `host` | `string` | A "glob-string" that matches on the `:authority` header of the incoming request. If not set, it will match on all incoming requests. |
+| `path` | `string` | A "glob-string" that matches on the request path. If not provided, then it will match on all incoming requests. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request header to determine if the `WebApplicationFirewall` is executed or not. |
+| `wafRef` | [WafReference][] | A reference to a `WebApplicationFirewall` to be applied against the request. |
+| `onError.statusCode` | `int` | Configure a response code to be sent to the downstream client when when a request matches the rule but there is a configuration or runtime error. By default, requests are allowed on error if this field is not configured. This covers runtime errors such as those caused by networking/request parsing as well as configuration errors such as if the `WebApplicationFirewall` that is referenced is misconfigured, cannot be found, or when its configuration cannot be loaded properly. Details about the errors can be found either in the `WebApplicationFirewall` status or container logs. |
+
+### HTTPHeaderMatch
+
+**Appears On**: [WafMatchingRule][]
+Checks if exact or regular expression matches a value in a request header to determine if the `WebApplicationFirewall` is executed or not.
+
+| **Field** | **Type** | **Description** |
+|------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `type` | `Enum`(`"Exact"`,`"RegularExpression"`) | Specifies how to match against the value of the header. Allowed values are `"Exact"`/`"RegularExpression"`. |
+| `name` | `string` | Name of the HTTP Header to be matched. Name matching MUST be case-insensitive. (See [https://tools.ietf.org/html/rfc7230#section-3.2][]) |
+| `value` | `string` | Value of HTTP Header to be matched. If type is `RegularExpression`, then this must be a valid regex with a length of at least 1. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. |
+
+### WafReference
+
+**Appears On**: [WafMatchingRule][]
+A reference to a `WebApplicationFirewall`
+
+| **Field** | **Type** | **Description** |
+|---------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `name` | Name of the `WebApplicationFirewall` being referenced
+| `namespace` | Namespace of the `WebApplicationFirewall`. This field is required. It must be a RFC 1123 label. Valid values include: `"example"`. Invalid values include: `"example.com"` - `"."` is an invalid character. The maximum allowed length is 63 characters, and the regex pattern `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` is used for validation. |
+
+## Web Application Firewall Usage Guides
+
+The following guides will help you get started using Web Application Firewalls
+
+- [Using Web Application Firewalls][]
+- [Rules for Web Application Firewalls][]
+- [Web Application Firewalls in Production][]
+
+[WafReference]: #wafreference
+[HTTPHeaderMatch]: #httpheadermatch
+[WafMatchingRule]: #wafmatchingrule
+[usage guides section]: #web-application-firewall-usage-guides
+[Using Web Application Firewalls]: ../../../../howtos/web-application-firewalls
+[Rules for Web Application Firewalls]: ../../../../howtos/web-application-firewalls-config
+[Web Application Firewalls in Production]: ../../../../howtos/web-application-firewalls-in-production
+[WebApplicationFirewalls]: ../webapplicationfirewall
+[https://tools.ietf.org/html/rfc7230#section-3.2]: https://tools.ietf.org/html/rfc7230#section-3.2
diff --git a/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-apikey.md b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-apikey.md
new file mode 100644
index 000000000..719f2d7a1
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-apikey.md
@@ -0,0 +1,76 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **APIKey Filter** Type (v3alpha1)
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied.
+For more information about how requests are matched to `Filter` resources and the order in which `Filters` are executed, please
+refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `APIKey Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `APIKey Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 APIKey Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## APIKey Filter API Reference
+
+To create an APIKey Filter, the `spec.type` must be set to `apikey`, and the `apikey` field must contain the configuration for your
+APIKey Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-apikey-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string
+ APIKey: APIKeyFilter # required
+ httpHeader: string # optional, default: `x-api-key`
+ keys: []APIKeyItem # required, min items: 1
+ - secretName: string # required
+```
+
+### APIKeyFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `httpHeader` | `string` | The name of the http header where the api-key will be found (always case-insensitive). By default it will use the `x-api-key` header. |
+| `keys` | \[\][APIKeyItem][] | The set of APIKeys that are used to check the whether the incoming request is valid. |
+
+### APIKeyItem
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `secretName` | `string` | Defines how to resolve the values of the keys. Currently the only supported way to resolve a key is via a local secret. APIKeys cannot use shared secrets in a different namespace than the `APIKey Filter` resource. |
+
+**Note about Secret formatting**:
+When supplying secrets to an API Key filter, the keys of the Secret do not matter, but the value of your API Key must be [base64][] encoded.
+
+For example, if you want to create a secret for the API Key value `example-api-key-value`, the secret should look like:
+
+```yaml
+---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apikey-filter-keys
+ type: Opaque
+ data:
+ any-name-you-want: ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+```
+
+You can specify as many API Keys in the Secret as you like.
+
+[APIKeyItem]: #apikeyitem
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 APIKey Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-apikey
+[base64]: https://en.wikipedia.org/wiki/Base64
diff --git a/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-external.md b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-external.md
new file mode 100644
index 000000000..53b2968f9
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-external.md
@@ -0,0 +1,110 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **External Filter** Type (v3alpha1)
+
+The `External Filter` allows users to provide their own Kubernetes Service speaking the [ext_authz protocol][].
+$productName$ will send a request to this "External Service" that contains a copy of the incoming request. The External Service will then be able
+to examine details of the incoming request, make changes to its headers, and allow or reject it by sending back a response to $productName$.
+The external service is free to perform any logic it likes before responding to $productName$, allowing for custom filtering and
+processing on incoming requests. The `External Filter` may be used along with any of the other Filter types. For more information about
+how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `External Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `External Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 External Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## External Filter API Reference
+
+To create an External Filter, the `spec.type` must be set to `external`, and the `external` field must contain the configuration for your
+external filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-external-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ External: ExternalFilter # required
+ auth_service: string # required
+ tls: bool # optional, default=true if auth_service starts with https://
+ tlsConfig: TLSConfig # optional
+ certificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ caCertificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ proto: Enum # optional, default=http
+ timeout_ms: int # optional, default=5000
+ allowed_request_headers: []string # optional
+ allowed_authorization_headers: []string # optional
+ add_linkerd_headers: bool # optional
+ path_prefix: string # optional
+ include_body: IncludeBody # optional
+ max_bytes: int # required
+ allow_partial: bool # required
+ protocol_version: Enum # required
+ status_on_error: # optional
+ code: int # required
+ failure_mode_allow: bool # optional
+
+```
+
+### ExternalFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `auth_service` | `string` | Identifies the external auth service to talk to. The format of this field is `scheme://host:port` where `scheme://` and `:port` are optional. The scheme-part, if present, must be either `http://` or `https://`; if the scheme-part is not present, it behaves as if `http://` is given. The scheme-part influences the default value of the `tls` field and the default value of the port-part. The host-part must be the [namespace-qualified DNS name][] of the service you want to use for authentication. |
+| `tls` | `bool` | Controls whether to use TLS or cleartext when speaking to the external auth service. The default is based on the scheme-part of the `auth_service` |
+| `tlsConfig` | [TLSConfig][] | Configures tls settings between $productName$ and the configured AuthService |
+| `proto` | `Enum` (`"http"`/`"grpc"`) | The type of [ext_authz protocol][] to use when communicating with the External Service. It is recommended to use "grpc" over "http" due to supporting additional capabilities. |
+| `timeout_ms` | `int` | The total maximum duration in milliseconds for the request to the external auth service, before triggering `status_on_error` or `failure_mode_allow` |
+| `allowed_request_headers` | `[]string` | Only applies when `proto: http`. Lists the headers (case-insensitive) that are copied from the incoming request to the request made to the external auth service. In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Cookie`, `From`, `Proxy-Authorization`, `User-Agent`, `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto` |
+| `allowed_authorization_headers` | `[]string` | Only applies when `proto: http`. Lists the headers (case-insensitive) that are copied from the response from the external auth service to the request sent to the upstream backend service (if the external auth service indicates that the request to the upstream backend service should be allowed). In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Location`, `Proxy-Authenticate`, `Set-cookie`, `WWW-Authenticate` |
+| `add_linkerd_headers` | `bool` | Only applies when `proto: http`. When true, in the request to the external auth service, adds an `l5d-dst-override` HTTP header that is set to the hostname and port number of the external auth service |
+| `path_prefix` | `string` | Only applies when `proto: http`. Prepends a string to the request path of the request when sending it to the external auth service. By default this is empty, and nothing is prepended. For example, if the client makes a request to `/foo`, and `path_prefix: /bar`, then the path in the request made to the external auth service will be `/foo/bar` |
+| `include_body` | [IncludeBody][] | Controls how much to buffer the request body to pass to the external auth service, for use cases such as computing an HMAC or request signature. If `include_body` is unset, then the request body is not buffered at all, and an empty body is passed to the external auth service. If include_body is not null, the `max_bytes` and `allow_partial` subfields are required. Unfortunately, in order for `include_body` to function properly, the `AuthService` resource must be edited to have its own `include_body` set with `max_bytes` greater than the largest `max_bytes` used by any `External Filter`, and `allow_partial: true` |
+| `status_on_error.code` | `int` | Controls the status code returned when unable to communicate with external auth service. This is ignored if `failure_mode_allow: true` |
+| `failure_mode_allow` | `bool` | Controls whether to allow or reject requests when there is an error communicating with the external auth service; a value of true allows the request through to the upstream backend service, a value of false returns a `status_on_error.code` response to the client |
+| `protocol_version` | `Enum (v3)` | Only applies when `proto: grpc`. Indicates the version of the transport protocol that the `External Filter` is using. Allowed values are `v3` and `v2`. `protocol_version` was used in previous versions of $productName$ to note the protocol used by the gRPC service for the `External Filter`. $productName$ 3.x is running an updated version of Envoy that has dropped support for the `v2` protocol, so starting in 3.x, if `protocol_version` is not specified, the default value of `v2` will cause an error to be posted and a static response will be returned. Therefore, you must set it to `protocol_version: v3`. If upgrading from a previous version, you will want to set it to `v3` and ensure it is working before upgrading to $productName$ 3.x. The default value for `protocol_version` remains `v2` in the `getambassador.io/v3alpha1` CRD specifications to avoid making breaking changes outside of a CRD version change |
+
+### IncludeBody
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `maxBytes` | `int` | Sets the number of bytes of the request body to buffer over to the External Service |
+| `allowPartial` | `bool` | Indicates whether the included body can be a partially buffered body or if the complete buffered body is expected. If not partial then a `HTTP 413` error is returned by Envoy. |
+
+### TLSConfig
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|-----------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `certificate.fromSecret` | SecretReference | Configures $productName$ to use the provided certificate to present to the server when connecting. Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret][] that contains the private key and public certificate that will be presented to the AuthService. Secret namespace defaults to Filter namespace if not set |
+| `caCertificate.fromSecret` | SecretReference | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret][] that contains the `tls.crt` key with the CA Certificate. Secret namespace defaults to Filter namespace if not set |
+
+
+[ExternalFilter]: #externalfilter
+[IncludeBody]: #includebody
+[TLSConfig]: #tlsconfig
+[ext_authz protocol]: ../../../../topics/running/services/ext-authz
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 External Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-external
+[Kubernetes Secret]: https://kubernetes.io/docs/concepts/configuration/secret
+[namespace-qualified DNS name]: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services
diff --git a/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-jwt.md b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-jwt.md
new file mode 100644
index 000000000..7bcb69479
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-jwt.md
@@ -0,0 +1,176 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **JWT Filter** Type (v3alpha1)
+
+The `JWT Filter` performs JWT validation on a [bearer token][] present in the HTTP header. If the bearer token JWT doesn't validate,
+or has insufficient scope, an RFC 6750-complaint error response with a `www-authenticate` header is returned. The list of acceptable
+signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in the `Filter` configuration. Only RSA and `none`
+algorithms are supported. For more information about how requests are matched to `Filter` resources and the order in which
+`Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `JWT Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `JWT Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 JWT Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## JWT Filter API Reference
+
+To create a JWT Filter, the `spec.type` must be set to `jwt`, and the `jwt` field must contain the configuration for your
+JWT Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-jwt-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ JWT: JWTFilter # required
+ jwksURI: string # required, (unless the only validAlgorithm is "none")
+ insecureTLS: bool # optional, default=false
+ renegotiateTLS: Enum # optional, default="never"
+ validAlgorithms: []string # optional, default is "all supported algos except for 'none'"
+ audience: string # optional (unless `requireAudience: true`)
+ requireAudience: bool # optional, default=false
+ issuer: string # optional (unless `requireIssuer: true`)
+ requireIssuer: bool # optional, default=false
+ requireExpiresAt: bool # optional, default=false
+ leewayForExpiresAt: Duration # optional
+ requireNotBefore: bool # optional, default=false
+ leewayForNotBefore: Duration # optional
+ requireIssuedAt: bool # optional, default=false
+ leewayForIssuedAt: Duration # optional
+ maxStale: Duration # optional
+ injectRequestHeaders: AddHeaderTemplate # optional
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ errorResponse: ErrorResponse # optional
+ contentType: "string" # deprecated, use 'headers' instead
+ realm: "string" # optional, default="{{.metadata.name}}.{{.metadata.namespace}}"
+ headers: # optional, default=[{name: "Content-Type", value: "application/json"}]
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ bodyTemplate: "string" # optional, default=`{{ . | json "" }}`
+```
+
+### JWTFilter
+
+| **Field** | **Type** | **Description** |
+|-------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `jwksURI` | `string` | A URI that returns the JWK Set per RFC 7517. This is required unless validAlgorithms=["none"], in that case verifying the signature of the token is disabled. This is considered unsafe and is discouraged when receiving tokens from untrusted sources. |
+| `validAlgorithms` | \[\][ValidAlgorithms][](`Enum`) | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `audience` | `string` | Identifies the recipient that the JWT is intended for and will be used to validate the provided token is intended for the configured audience. If not provided then `aud` claim on incoming token is not validated and will be considered valid. If `aud` is unset on the token by default it will be considered valid even if it doesn't match the audience value. To enforce that a token has the aud claim, then set `requireAudience: true`. |
+| `requireAudience` | `bool` | Modifies the validation behavior for when the audience claim (aud) is unset on the incoming token. `false` (default) => if aud claim is unset then claim is considered valid. `true` => if aud claim is unset then claim/token are invalid |
+| `issuer` | `string` | Identifies the expected AuthorizationServer that isssued the token. If not provided then the issuer claim will not be validated. If `issuer` is unset on the token by default it will be considered valid even if it doesn't match the expected issuer value. To enforce that a token has the issuer claim, then set `requireIssuer: true`. |
+| `requireIssuer` | `bool` | Modifies the validation behavior for when the issuer claim (iss) is unset on the incoming token. `false` (default) => if aud claim is unset on incoming token then claim is considered valid `true` => if exp claim is unset then claim is invalid |
+| `requireExpiresAt` | `bool` | Modifies the validation behavior for when the expiresAt claim (exp) is unset on the incoming token. `false` (default) => if exp claim is unset on incoming token then claim is valid `true` => if exp claim is unset then claim/token are invalid |
+| `leewayForExpiresAt` | [Duration][] | Allows token expired by this much to still be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `requireNotBefore` | `bool` | Modifies the validation behavior for when the not before time claim (nbf) is unset on the incoming token. `false` (default) => if `nbf` claim is unset on incoming token then claim is valid `true` => if `nbf` claim is unset then claim/token are invalid |
+| `leewayForNotBefore` | [Duration][] | Allows tokens that shouldn't be used until this much in the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `requireIssuedAt` | `bool` | Modifies the validation behavior for when the issuedAt claim (iat) is unset on the incoming token. `false` (default) => if `iat` claim is unset on incoming token then claim is valid `true` => if `iat` claim is unset then claim/token are invalid |
+| `leewayForIssuedAt` | [Duration][] | Allows tokens issued by this much into the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `injectRequestHeaders` | \[\][AddHeaderTemplate][] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream request headers as values. For example, attaching user email claim to a header from the token. |
+| `maxStale` | [Duration][] | Sets the duration that JWKs keys and OIDC discovery responses will be cached, ignoring any caching headers when configured |
+| `insecureTLS` | `bool` | Disables TLS verification for cases when jwksURI begins with `https://`. |
+| `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the JWTFilter will renegotiateTLS with the `jwksURI` server and if so what supported method of renegotiation will be used. |
+| `errorResponse` | [CustomErrorResponse][] | Allows setting a custom Response to the downstream client when an invalid JWT is received. |
+
+### Duration
+
+**Appears On**: [JWTFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ValidAlgorithms
+
+**Appears On**: [JWTFilter][]
+The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not
+in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP,
+as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided
+to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected.
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+
+### AddHeaderTemplate
+
+**Appears On**: [JWTFilter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream
+request headers as values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+`value` is the value of the header to set and is evaluated as a special GoLang Template.
+This allows the header value to be set based on the JWT value. The value is specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.token.Raw` → The raw JWT (`string`)
+- `.token.Header` → The JWT header, as parsed JSON (`map[string]interface{}`)
+- `.token.Claims` → The JWT claims, as parsed JSON (`map[string]interface{}`)
+- `.token.Signature` → The token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+Also available to the template are [the standard functions available to Go text/templates][], as well as:
+
+- a `hasKey` function that takes the a string-indexed map as arg1, and returns whether it contains the key arg2. (This is the same as [the Sprig function of the same name][].)
+- a `doNotSet` function that causes the result of the template to be discarded, and the header field to not be adjusted. This is useful for only conditionally setting a header field; rather than setting it to an empty string or `""`. Note that this does not unset an existing header field of the same name and could be a potential security vulnerability depending on how this is used if an untrusted client spoofs these headers.
+
+
+ Any headers listed will override (not append to) the original request header with that name.
+
+
+### CustomErrorResponse
+
+**Appears On**: [JWTFilter][]
+Allows setting a custom Response to the downstream client when an invalid JWT is received.
+
+| **Field** | **Type** | **Description** |
+|------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `realm` | `string` | Indicates the scope of protection or the application that is checking the token. By default, this is set to the fully qualified name of the `JWT Filter` as `"{name}.{namespace}"` to identify which filter rejected the error. This can be overriden to provide more relevant information to end-users. |
+| `bodyTemplate` | `string` (GoLang Template) | Golang `text/template` string that will be evaluated and used to build the format returned. |
+| `headers` | \[\][AddHeaderTemplate][] | Allows providing additional http response headers for the error response. The current maximum is 16 headers, which aligns with the Gateway-API and modified headers on HTTPRoutes. |
+
+`bodyTemplate` specifies body of the error response returned to the downstream client; specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.status_code` → The HTTP status code to be returned (`int`)
+- `.httpStatus` → An alias for .status_code (`int`, hidden from `{{ . | json "" }}`)
+- `.message` → The error message (`string`)
+- `.error` → The raw Go error object that generated .message (`error`, hidden from `{{ . | json "" }}`)
+- `.error.ValidationError` → The JWT validation error, will be nil if the error is not purely JWT validation (`jwt.ValidationError` insufficient scope, malformed or missing Authorization header)
+- `.request_id` → The Envoy request ID, for correlation (`string`, hidden from `{{ . | json "" }}` unless `.status_code` is in the `5xx` range)
+- `.requestId` → An alias for .request_id (`string`, hidden from `{{ . | json "" }}`)
+
+Also availabe to the template are [the standard functions available to Go text/templates][], as well as:
+
+- A `json` function that formats arg2 as JSON, using the arg1 string as the starting indentation. For example, the template `{{ json "indent>" "value" }}` would yield the string `indent>"value"`.
+
+[JWTFilter]: #jwtfilter
+[ValidAlgorithms]: #validalgorithms
+[AddHeaderTemplate]: #addheadertemplate
+[CustomErrorResponse]: #customerrorresponse
+[Duration]: #duration
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 JWT Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-jwt
+[bearer token]: https://datatracker.ietf.org/doc/html/rfc6750
+[Go text/template string]: https://pkg.go.dev/text/template
+[the standard functions available to Go text/templates]: https://pkg.go.dev/text/template#hdr-Functions
+[the Sprig function of the same name]: https://masterminds.github.io/sprig/dicts.html#haskey
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-oauth2.md b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-oauth2.md
new file mode 100644
index 000000000..0b55315a4
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-oauth2.md
@@ -0,0 +1,368 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **OAuth2 Filter** Type (v3alpha1)
+
+The OAuth2 Filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+
+
+
+This doc is an overview of all the fields on the `OAuth2 Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `OAuth2 Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 OAuth2 Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## OAuth2 Filter API Reference
+
+To create an OAuth2 Filter, the `spec.type` must be set to `oauth2`, and the `oauth2` field must contain the configuration for your
+OAuth2 filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-oauth2-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ OAuth2: OAuth2 # required
+ authorizationURL: string # required
+
+ #------------------------------------------------------------------#
+ # OAuth Client settings #
+ #------------------------------------------------------------------#
+ expirationSafetyMargin: Duration # optional
+ grantType: Enum # optional, default="AuthorizationCode"
+ clientAuthentication: # optional
+ method: "enum" # optional, default="HeaderPassword"
+ jwtAssertion: # optional if `method: "JWTAssertion"`, forbidden otherwise
+ setClientID: bool # optional, default=false
+ audience: string # optional, default is to use the token endpoint from the authorization URL
+ signingMethod: Enum # optional, default="RS256"
+ lifetime: Duration # optional, default="1m"
+ setNBF: bool # optional, default=false
+ nbfSafetyMargin: Duration # optional, default=0s
+ setIAT: bool # optional, default=false
+ otherClaims: # optional, default={}
+ "string": anything
+ otherHeaderParameters: # optional; default={}
+ "string": anything
+
+ ## OAuth Client settings when `grantType: "AuthorizationCode"`
+ clientURL: string # deprecated, use 'protectedOrigins' instead
+ protectedOrigins: []ProtectedOrigin # required, minItems: 1
+ - origin: string # required
+ internalOrigin: string # optional, default is to just use the 'origin' field
+ includeSubdomains: bool # optional, default=false
+ useSessionCookies: # optional, default={ value: false }
+ value: bool # optional, default=true
+ ifRequestHeader: # optional, default to apply "useSessionCookies.value" to all requests
+ name: string # required
+ negate: bool # optional, default=false
+ value: string # optional, default is any non-empty string
+ valueRegex: string # optional, default is any non-empty string
+ clientSessionMaxIdle: Duration # optional, default is to use the access token lifetime or 14 days if a refresh token is present
+ postLogoutRedirectURI: string # optional
+ extraAuthorizationParameters: map[string]string # optional, default={}
+ "string": "string"
+
+ ## OAuth Client settings when `grantType: "AuthorizationCode"/"Password"`
+ clientID: string # required
+ secret: string # required (unless secretName is set)
+ secretName: string # required (unless secret is set)
+ secretNamespace: string # optional, default is the same namespace as the Filter
+
+ #------------------------------------------------------------------#
+ # OAuth Resource Server settings #
+ #------------------------------------------------------------------#
+ allowMalformedAccessToken: bool # optional, default=false
+ accessTokenValidation: Enum # optional, default="auto"
+ accessTokenJWTFilter: # optional
+ name: string # required
+ namespace: string # optional, default is the same namespace as the Filter
+ inheritScopeArgument: bool # optional, default=false
+ stripInheritedScope: bool # optional, default=false
+ arguments: JWTFilterArguments # optional
+ injectRequestHeaders: # optional
+ - name: string # required
+ value: string # required
+
+ #------------------------------------------------------------------#
+ # HTTP client settings for talking with the identity provider #
+ #------------------------------------------------------------------#
+ insecureTLS: bool # optional, default=false
+ renegotiateTLS: Enum # optional, default="never"
+ maxStale: Duration # optional, default="0"
+```
+
+### OAuth2Filter
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `authorizationURL` | `string` | Identity Provider Issuer URL which hosts the OpenID provider well-known configurartion. The URL must be an absolute URL. Per [OpenID Connect Discovery 1.0][] the configuration must be provided in a json document at the path `/.well-known/openid-configuration`. This is used by the OAuth2 Filter for determining things like the AuthorizationEndpoint, TokenEndpoint, JWKs endpint, etc... |
+| `expirationSafetyMargin` | [Duration][] | Sets a buffer to check if the Token is expired or is going to expire within the safety margin. This is to ensure the application has enough time to reauthenticate to adjust for clock skew and network latency. By default, no safety margin is added. If a token is received with an expiration less than this field, then the token is considered to already be expired. |
+| `grantType` | `Enum`(`"AuthorizationCode"`,`"ClientCredentials"`,`"Password"`,`"ResourceOwner"`) | Sets the Authorization Flow that the filter will use to authenticate the incoming request. |
+| `clientAuthentication` | [ClientAuthentication][] | Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password. |
+| `protectedOrigins` | \[\][ProtectedOrigin][] | (You determine these, and must register them with your identity provider) Identifies hostnames that can appropriately set cookies for the application. Only the scheme (`https://`) and authority (`example.com:1234`) parts are used; the path part of the URL is ignored |
+| `useSessionCookies` | [SessionCookies][] | By default, any cookies set by $productName$ will be set to expire when the session expires naturally. `useSessionCookies` may be used to cause session cookies to be used instead |
+| `clientSessionMaxIdle` | [Duration][] | Controls how long the session held by $productName$'s OAuth client will last until we automatically expire it. $productName$ creates a new session when submitting requests to the upstream backend server and sets a cookie containing the sessionID. When a user makes a request to a backend service protected by the OAuth2 Filter, the OAuth Client in Ambassador Edge Stack will use the sessionID contained in the cookie to fetch the access token (and optional refresh token) for the current session so that it can be used when submitting a request to the upstream backend service. This session has a limited lifetime before it expires or extended, prompting the user to log back in. Setting a `clientSessionMaxIdle` duration is useful when your IdP is configured to return a refresh token along with an access token from your IdP's authorization server. `clientSessionMaxIdle` can be set to match Ambassador Edge Stack OAuth client's session lifetime to the lifetime of the refresh token configured within the IdP. If this is not set, then we tie the OAuth client's session lifetime to the lifetime of the access token received from the IdP's authorization server when no refresh token is also provided. If there is a refresh token, then by default we set it to be 14 days |
+| `postLogoutRedirectURI` | `string` | Set this field to a valid URL to have $productName$ redirect there upon a successful logout. You must register the following endpoint with your IDP as the Post Logout Redirect `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect`. This informs your IDP to redirect back to $productName$ once the IDP has cleared the session data. Once the IDP has redirected back to $productName$, this clears the local $productName$ session information before redirecting to the destination specified by the `postLogoutRedirectURI` value. If Post Logout Redirect is configured in your IDP to `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` then, after a successful logout, a redirect is issued to the URL configured in `postLogoutRedirectURI`. If `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` is configured as the Post Logout Redirect in your IDP, but `postLogoutRedirectURI` is not configured in $productName$, then your IDP will error out as it will be expecting specific instructions for the post logout behavior. Refer to your IDP’s documentation to verify if it supports Post Logout Redirects. For more information on `post_logout_redirect_uri functionality`, refer to the [OpenID Connect RP-Initiated Logout 1.0 specs](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) |
+| `extraAuthorizationParameters`| `map`\[`string`\]`string` | Extra (non-standard or extension) OAuth authorization parameters to use. It is not valid to specify a parameter used by OAuth itself ("response_type", "client_id", "redirect_uri", "scope", or "state") |
+| `clientID` | `string` | The Client ID you get from your identity provider |
+| `secret` | `string` | The client secret you get from your identity provider as a string. It is invalid to configure both `secret` and `secretName` |
+| `secretName` | `string` | The client secret you get from your identity provider as a Kubernetes `generic` Secret, named by `secretName`/`secretNamespace`. The Kubernetes secret must of the `generic` type, with the value stored under the key`oauth2-client-secret`. If `secretNamespace` is not given, it defaults to the namespace of the Filter resource. It is invalid to configure both `secret` and `secretName` |
+| `secretNamespace` | `string` | The client secret you get from your identity provider as a Kubernetes `generic` Secret, named by `secretName`/`secretNamespace`. The Kubernetes secret must of the `generic` type, with the value stored under the key`oauth2-client-secret`. If `secretNamespace` is not given, it defaults to the namespace of the Filter resource. It is invalid to configure both `secret` and `secretName` |
+| `allowMalformedAccessToken` | `bool` | Allow any access token, even if they are not RFC 6750-compliant. |
+| `accessTokenValidation` | `Enum`(`"jwt"`,`"userinfo"`,`"auto"`) | How to verify the liveness and scope of Access Tokens issued by the identity provider. Empty or unset is equivalent to `"auto"` |
+| `accessTokenJWTFilter` | [AccessTokenJWTFilter][] | Used to identify a JWT Filter to use for validating access token JWTs. It is an error to point at a Filter that is not a JWT filter |
+| `injectRequestHeaders` | \[\][AddHeaderTemplate][] | injects HTTP header fields in to the request before sending it to the upstream service; where the header value can be set based on the JWT value. If an OAuth2 filter is chained with a JWT filter with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the OAuth2 filter will populate the value. The value is specified as a Go `text/template` string |
+| `insecureTLS` | `bool` | disables TLS verification when speaking to an identity provider with an `https://` `authorizationURL`. This is discouraged in favor of either using plain `http://` or [installing a self-signed certificate][] |
+| `renegotiateTLS` | `Enum`(`"never"`,`"onceAsClient"`,`"freelyAsClient"`) | Allows a remote server to request TLS renegotiation |
+| `maxStale` | [Duration][] | How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also **ignores the `no-store` and `no-cache` Cache-Control directives on responses**. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Setting to 0 means that it will default back to the identity provider's default cache settings as specified by the Cache-Control directives on responses which may include no caching depending if the identity provider sets the `no-cache` and `no-store` directives. Note that if you are reusing the same `authorizationURL` and `jwksURI` across different OAuth and JWT filters respectively, then you MUST set `maxStale` as a consistent value on each filter to get predictable caching behavior |
+
+**`grantType` options**:
+
+- `"AuthorizationCode"`: Authenticate by redirecting to a login page served by the identity provider.
+- `"Password"`: Authenticate by requiring `X-Ambassador-Username` and `X-Ambassador-Password` on all incoming requests, and use them to authenticate with the identity provider using the OAuth2 Resource Owner Password Credentials grant type.
+- `"ClientCredentials"`: Authenticate by requiring that the incoming HTTP request include as headers the credentials for Ambassador to use to authenticate to the identity provider.
+ - The type of credentials needing to be submitted depends on the `clientAuthentication.method` (below):
+ - For `"HeaderPassword"` and `"BodyPassword"`, the headers `X-Ambassador-Client-ID` and `X-Ambassador-Client-Secret` must be set.
+ - For `"JWTAssertion"`, the `X-Ambassador-Client-Assertion` header must be set to a JWT that is signed by your client secret, and conforms with the requirements in RFC 7521 section 5.2 and RFC 7523 section 3, as well as any additional specified by your identity provider.
+
+**`accessTokenValidation` options**:
+
+- `"jwt"`: Validates the Access Token as a JWT.
+
+ - By default: It accepts the RS256, RS384, or RS512 signature algorithms, and validates the signature against the JWKS from
+OIDC Discovery. It then validates the `exp`, `iat`, `nbf`, `iss` (with the Issuer from OIDC Discovery), and `scope` claims: if present,
+none of the scope values are required to be present. This relies on the identity provider using non-encrypted signed JWTs as
+Access Tokens, and configuring the signing appropriately
+ - This behavior can be modified by delegating to [JWT Filter][] with `accessTokenJWTFilter`:
+
+- `"userinfo"`: Validates the access token by polling the OIDC UserInfo Endpoint. This means that $productName$ must initiate
+an HTTP request to the identity provider for each authorized request to a protected resource. This performs poorly,
+but functions properly with a wider range of identity providers. It is not valid to set `accessTokenJWTFilter` if
+`accessTokenValidation`: `userinfo`.
+
+- `"auto"` attempts to do `"jwt"` validation if any of these conditions are true:
+ - `accessTokenJWTFilter` is set
+ - `grantType` is `"ClientCredentials"`
+ - the Access Token parses as a JWT and the signature is valid,
+ - If none of the above conditions are satisfied, it falls back to `"userinfo"` validation.
+
+### Duration
+
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ClientAuthentication
+
+Configures how Ambassador uses the `clientID` and `secret` to authenticate itself to the identity provider
+
+| **Field** | **Type** | **Description** |
+|----------------|----------------------------------|---------------------------------------------------------------------------------------------------------|
+| `method` | `Enum`(`"HeaderPassword"`,`"BodyPassword"`,`"JWTAssertion"`) | Defines the type of client authentication that will be used |
+| `jwtAssertion` | [JWTAssertion][] | This field is only used when `method: "JWTAssertion"`. Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow. |
+
+`method` options:
+
+- `"HeaderPassword"`: Treat the client secret as a password, and pack that in to an HTTP header for HTTP Basic authentication.
+- `"BodyPassword"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+- `"JWTAssertion"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+
+### JWTAssertion
+
+Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|-------------------------|---------------------------------------------------------------------------------------------------------|
+| `setClientID` | `bool` | Whether to set the Client ID as an HTTP parameter; setting it as an HTTP parameter is optional (per RFC 7521 §4.2) because the Client ID is also contained in the JWT itself, but some identity providers document that they require it to also be set as an HTTP parameter anyway. |
+| `audience` | `string` | This field is ignored when `grantType: "ClientCredentials"`. The audience your IDP requires for authentication. If not set then the default will be to use the token endpoint from the OIDC discovery document. |
+| `signingMethod` | [ValidAlgorithms][] | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `lifetime` | [Duration][] | This field is ignored when `grantType: "ClientCredentials"`. The lifetime of the generated JWT; just enough time for the request to the identity provider to complete (plus possibly an extra allowance for clock skew). |
+| `setNBF` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "nbf" ("Not Before") claim in the generated JWT. |
+| `nbfSafetyMargin` | [Duration][] | This field is only used when `setNBF: true` The safety margin to build-in to the "nbf" claim, to allow for clock skew between ambassador and the identity provider. |
+| `setIAT` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "iat" ("Issued At") claim in the generated JWT. |
+| `otherClaims` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Key/value pairs that will be add to the JWT sent for client Auth to the Identity Provider |
+| `otherHeaderParameters` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Any extra JWT header parameters to include in the generated JWT non-standard claims to include in the generated JWT; only the "typ" and "alg" header parameters are set by default. |
+
+### ValidAlgorithms
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+ - The secret must be a PEM-encoded Eliptic Curve private key
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+ - The secret is a raw string of bytes; it can contain anything
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+ - The secret must be a PEM-encoded RSA private key
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+ - The secret must be a PEM-encoded RSA private key
+
+### ProtectedOrigin
+
+You determine these, and must register them with your identity provider. Identifies hostnames that can
+appropriately set cookies for the application. Only the scheme (`https://`) and authority (`example.com:1234`) parts are used; the
+path part of the URL is ignored. You will need to register each origin in `protectedOrigins` as an authorized callback endpoint with your identity provider. The URL
+will look like `{{ORIGIN}}/.ambassador/oauth2/redirection-endpoint`.
+
+
+
+
+If you provide more than one `protectedOrigin`, all share the same
+authentication system, so that logging into one origin logs you
+into all origins; to have multiple domains that have separate
+logins, use separate `Filter`s.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|------------|---------------------------------------------------------------------------------------------------------|
+| `origin` | `string` | The absolute URL (schema://hostname) that is protected by the OAuth2 Filter |
+| `includeSubdomains` | `bool` | Enables protecting sub-domains of the domain identified in the Origin field. Example, when `Origin=https://example.com` then the subdomain of `https://app.example.com` would be watched. |
+| `allowedInternalOrigins` | `[]string` | Indentifies a list of allowed internal origins that were set by a downstream proxy via a host header rewrite. The origins identified in this list ensures the request is allowed and will ensure it redirects correctly to the upstream origin. For example, a downstream client will communicate with an origin of `https://example.com` but then an internal proxy will do a rewrite so that the host header received by Edge Stack is `http://example.internal`. |
+
+**Note about `allowedInternalOrigins`**: This field is primarily used to allow you to tell $productName$ that there is another gateway
+in front of $productName$ that rewrites the Host header, so that on the internal network between that gateway and $productName$, the
+origin appears to be `allowedInternalOrigins` instead of `origin`. As a special-case the scheme and/or authority of the `allowedInternalOrigins`
+may be `"*"`, which matches any scheme or any domain respectively.
+Using `"*"` is most useful in configurations with exactly one protected origin; in such a configuration, $productName$ doesn't need
+to know what the origin looks like on the internal network, just that a gateway in front of $productName$ is rewriting it.
+It is invalid to use `"*"` with `includeSubdomains: true`.
+
+For example, if you have a gateway in front of $productName$ handling traffic for `myservice.example.com`, terminating TLS and routing
+that traffic to $productName$ with the name `example.internal`, you might write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - http://example.internal
+```
+
+or, to avoid being fragile to renaming example.internal to something else, since there are not multiple origins that the `Filter` must
+distinguish between, you could instead write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - "*://*"
+```
+
+### AddHeaderTemplate
+
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+The header value can be set based on the JWT value. If an `OAuth2 Filter` is chained with a [JWT filter][] with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the `OAuth2 Filter` will populate the value. The value is specified as a [Go text/template][] string, with the following data made available to it:
+
+- `.token.Raw` → The access token raw JWT (`string`)
+- `.token.Header` → The access token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.token.Claims` → The access token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.token.Signature` → The access token signature (`string`)
+- `.idToken.Raw` → The raw id token JWT (`string`)
+- `.idToken.Header` → The id token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Claims` → The id token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Signature` → The id token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+### SessionCookies
+
+By default, any cookies set by the $productName$ will be set to expire when the session expires naturally. The
+`useSessionCookies` setting may be used to cause session cookies to be used instead.
+
+
+
+- Normally cookies are set to be deleted at a specific time; session cookies are deleted whenever the user closes their web
+browser. This may mean that the cookies are deleted sooner than normal if the user closes their web browser; conversely, it may
+mean that cookies persist for longer than normal if the use does not close their browser.
+- The cookies being deleted sooner may or may not affect user-perceived behavior, depending on the behavior of the identity provider.
+- Any cookies persisting longer will not affect behavior of the system; Ambassador Edge Stack validates whether the session is expired when considering the cookie.
+
+If `useSessionCookies` is non-`null`, then:
+
+- By default it will have the cookies for all requests be session cookies or not according to the `useSessionCookies.value` sub-argument.
+- Setting the `useSessionCookies.ifRequestHeader` sub-argument tells it to use `useSessionCookies.value` for requests that match the condition, and `!useSessionCookies.value` for requests don't match.
+
+When determining if a request matches, it looks at the HTTP header field named by `useSessionCookies.ifRequestHeader.name` (case-insensitive), and checks if it is either set to (if `useSessionCookies.ifRequestHeader.negate: false`) or not set to (if `useSessionCookies.ifRequestHeader.negate: true`)...
+
+- a non-empty string (if neither `useSessionCookies.ifRequestHeader.value` nor `useSessionCookies.ifRequestHeader.valueRegex` are set)
+- the exact string `value` (case-sensitive) (if `useSessionCookies.ifRequestHeader.value` is set)
+- a string that matches the regular expression `useSessionCookies.ifRequestHeader.valueRegex` (if `valueRegex` is set). This uses [RE2][] syntax (always, not obeying `regex_type` in the `Module`) but does not support the `\C` escape sequence.
+- (it is invalid to have both `value` and `valueRegex` set)
+
+| **Field** | **Type** | **Description** |
+|-------------------|---------------------------|-----------------------------------------------------------------------------------|
+| `value` | `bool` |
+| `ifRequestHeader` | [HTTPHeaderMatch][] |
+
+### HTTPHeaderMatch
+
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of the HTTP Header to be matched. Only one of `value` or `valueRegex` can be configured |
+| `valueRegex` | `string` | Regex expression for matching the value of the HTTP Header. Only one of `value` or `valueRegex` can be configured. This uses [RE2][] syntax (always, not obeying `regex_type` in the `ambassador Module`) but does not support the `\C` escape sequence. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### AccessTokenJWTFilter
+
+**Appears On**: [OAuth2Filter][]
+Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes
+
+| **Field** | **Type** | **Description** |
+|------------------------|-------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `namespace` | `string` | Namespace of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `inheritScopeArgument`:| `bool` | Will use the same scope as set on the FilterPolicy OAuth2Arguments. If the JWTFilter sets a scope as well then the union of the two will be used. |
+| `stripInheritedScope` | `bool` | Determines whether or not to santized a scope that is formatted as an URI and was inherited from the FilterPolicy OAuth2Arguments. This will be done prior to passing it along to the referenced JWTFilter. This requires that InheritScopeArgument is true. |
+| `arguments` | [JWTArguments][] | Defines the input arguments that can be set for a JWTFilter. |
+
+### JWTArguments
+
+Defines the input arguments that can be set for a JWTFilter.
+
+| **Field** | **Type** | **Description** |
+|------------|-------------|---------------------------------------------------------------------------------------------------------|
+| `scope` | `[]string` | A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope. |
+
+**Some notes about `scope`**:
+
+- If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed.
+- If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+- As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+- The ordering of scope values does not matter, and is ignored.
+
+[AddHeaderTemplate]: #addheadertemplate
+[Oauth2Filter]: #oauth2filter
+[AccessTokenJWTFilter]: #accesstokenjwtfilter
+[ClientAuthentication]: #clientauthentication
+[JWTArguments]: #jwtarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[JWTAssertion]: #jwtassertion
+[ValidAlgorithms]: #validalgorithms
+[ProtectedOrigin]: #protectedorigin
+[SessionCookies]: #sessioncookies
+[Duration]: #duration
+[installing a self-signed certificate]: ../../../../topics/using/filters/#filters-using-self-signed-certificates
+[JWT Filter]: ../filter-jwt
+[the v1alpha1 OAuth2 Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-oauth2
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
+[OpenID Connect Discovery 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
+[RE2]: https://github.com/google/re2/wiki/Syntax
diff --git a/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-plugin.md b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-plugin.md
new file mode 100644
index 000000000..264ed57b2
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter-plugin.md
@@ -0,0 +1,42 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Plugin Filter** Type (v3alpha1)
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled into a .so file,
+which you load into the Envoy Proxy container at `/etc/ambassador-plugins/${NAME}.so`. For more information about how requests are
+matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `Plugin Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `Plugin Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 Plugin Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## Plugin Filter API Reference
+
+To create a Plugin Filter, the `spec.type` must be set to `plugin`, and the `plugin` field must contain the configuration for your Plugin Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-plugin-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ plugin: PluginFilter # required
+ name: string # required
+```
+
+`name`: Indicates the compiled binaries name excluding the extension for the Plugin.
+Envoy Proxy will look for the .so file in the `/etc/ambassador-plugins` directory.
+For example, if `name: "example-plugin"` the .so file should be available at
+`"/etc/ambassador-plugins/example-plugin.so"` on the Envoy Proxy container.
+
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 Plugin Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-plugin
diff --git a/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter.md b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter.md
new file mode 100644
index 000000000..fb65cb023
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filter.md
@@ -0,0 +1,64 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **Filter** Resource (v3alpha1)
+
+The `Filter` custom resource works in conjunction with the [FilterPolicy custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending them to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests, such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute against those requests. Filters are largely used to add built-in authentication and security, but
+$productName$ also supports developing custom filters to add your own processing and logic.
+
+This doc is an overview of all the fields on the `Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 Filter api reference][].
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## v3alpha1 Filter API Reference
+
+Filtering is configured using `Filter` custom resources. The body of the resource `spec` depends on the filter type:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ JWT: JWTFilter # optional
+ OAuth2: OAuth2Filter # optional
+ APIKey: APIKeyFilter # optional
+ External: ExternalFilter # optional
+ plugin: PluginFilter # optional
+```
+
+### FilterSpec
+
+Other than `ambassador_id`, only one of the following fields may be configured. For example you cannot create a `Filter` with both
+`JWT` and `External`.
+
+| **Field** | **Type** | **Description** |
+|------------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------|
+| `ambassador_id` | \[\]`string` | Ambassador id accepts a list of strings that allow you to restrict which instances of $productName$ can use/view this resource. If `ambassador_id` is configured, then only Deployments of $productName$ with a matching `AMBASSADOR_ID` environment variable will be able to use this resource. |
+| `JWT` | [JWTFilter][] | Provides configuration for the JWT Filter type |
+| `OAuth2` | [OAuth2Filter][] | Provides configuration for the OAuth2 Filter type |
+| `APIKey` | [APIKeyFilter][] | Provides configuration for the APIKey Filter type |
+| `External` | [ExternalFilter][] | Provides configuration for the External Filter type |
+| `Plugin` | [PluginFilter][] | Provides configuration for the Plugin Filter type |
+
+[FilterPolicy custom resource]: ../filterpolicy
+[JWTFilter]: ../filter-jwt
+[PluginFilter]: ../filter-plugin
+[OAuth2Filter]: ../filter-oauth2
+[APIKeyFilter]: ../filter-apikey
+[ExternalFilter]: ../filter-external
+[the v1alpha1 Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter
diff --git a/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filterpolicy.md b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filterpolicy.md
new file mode 100644
index 000000000..af34f45ab
--- /dev/null
+++ b/docs/edge-stack/latest/custom-resources/getambassador/v3alpha1/filterpolicy.md
@@ -0,0 +1,139 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **FilterPolicy** Resource (v3alpha1)
+
+The `FilterPolicy` custom resource works in conjunction with the [Filter custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute on those requests.
+
+
+
+This doc is an overview of all the fields on the `FilterPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `FilterPolicy` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 FilterPolicy api reference][].
+
+
+ v3alpha1FilterPolicies can only be reference v3alpha1Filters.
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## FilterPolicy API Reference
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec: FilterPolicy
+ ambassador_id: []string # optional
+ rules: []FilterPolicyRule # required, minItems: 1
+ - host: string # required
+ path: string # required
+ precedence: int # optional
+ filters: []FilterReference # required, minItems: 1
+ - name: string # required
+ namespace: string # optional, default is the same namespace as the FilterPolicy
+ onDeny: Enum # optional, default="break"
+ onAllow: Enum # optional, default="continue"
+ ifRequestHeader: HTTPHeaderMatch # optional
+ name: string # required
+ value: string # optional, default is any non-empty string
+ valueRegex: string # optional, default is any non-empty string
+ negate: bool # optional, default=false
+ arguments: FilterArguments # optional
+```
+
+### FilterPolicy
+
+| **Field** | **Type** | **Description** |
+|------------------|----------------------------|-----------------------------------------------------------------------------------|
+| `ambassador_id` | \[\]`string` | Ambassador id accepts a list of strings that allow you to restrict which instances of $productName$ can use/view this resource. If `ambassador_id` is configured, then only Deployments of $productName$ with a matching `AMBASSADOR_ID` environment variable will be able to use this resource. |
+| `rules` | \[\][FilterPolicyRule][] | Set of matching rules that are checked against incoming request to determine which set of Filter's to apply. If no matches are found then the request is allowed through to the upstream service without executing any Filters. |
+
+### FilterPolicyRule
+
+Configures matching rules that are checked against incoming request to determine which `Filter` to apply (if any).
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-----------------------------------------------------------------------------------|
+| `host` | `string` | "glob-string" that matches on the `:authority` header of the incoming request. If not set it will match on all incoming requests. |
+| `path` | `string` | "glob-string" that matches on the request path. If not provided then it will match on all incoming requests. |
+| `precedence` | `int` | Allows forcing a precedence ordering on the rules. By default the rules are evaluated in the order they are in the `FilterPolicy.spec.rules` field. However, multiple FilterPolicy's can be applied to a cluster. To ensure that a specific ordering is enforced then using a precedence is an option. |
+| `filters` | \[\][FilterReference][] | List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths. |
+
+**Note:** The wildcard `*` is supported for both `path` and `host`.
+
+When multiple Filters are specified in a rule:
+
+- The filters are gone through in order
+- Each filter may either:
+ - return a direct HTTP *response*, intended to be sent back to the requesting HTTP client (normally *denying* the request from being forwarded to the upstream service) OR
+ - return a modification to make to the HTTP *request* before sending it to other filters or the upstream service (normally *allowing* the request to be forwarded to the upstream service with modifications).
+- If a filter has an `ifRequestHeader` setting, the filter is skipped
+ unless the request (including any modifications made by earlier
+ filters) has the HTTP header field `name`
+ set to (or not set to if `negate: true`):
+ - a non-empty string if neither `value` nor `valueRegex` are set
+ - the exact string `value` (case-sensitive) (if `value` is set)
+ - a string that matches the regular expression `valueRegex` (if
+ `valueRegex` is set). This uses [RE2][] syntax (always, not
+ obeying [`regex_type`][] in the Ambassador module) but does not
+ support the `\C` escape sequence.
+- Modifications to the request are cumulative; later filters have access to _all_ headers inserted by earlier filters.
+
+### FilterReference
+
+A refernce to a filter to be executed when an incoming request matches the `FilterPolicy` Rule
+
+| **Field** | **Type** | **Description** |
+|-------------------|-----------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name that identifies the Filter |
+| `namespace` | `string` | Kubernetes namespace that the Filter resides. It must be a RFC 1123 label. Valid values include: `"example"`, Invalid values include: `"example.com"` (`.` is an invalid character). This validation is based off of the [corresponding Kubernetes validation]. |
+| `onDeny` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `onAllow` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter allows the request. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not. |
+| `arguments` | [FilterArguments][] | Untyped map that allows for additional configuration specific to each filter to be provided |
+
+**onDeny Options**:
+
+- `"break"`: End processing, and return the response directly to
+ the requesting HTTP client. Later filters are not called. The request is not forwarded to the upstream service.
+- `"continue"`: Continue processing. The request is passed to the
+ next filter listed; or if at the end of the list, it is forwarded to the upstream service. The HTTP response returned from the filter is discarded.
+
+**onAllow Options**:
+
+- `"break"`: Apply the modification to the request, then end filter processing, and forward the modified request to the upstream service. Later filters are not called.
+- `"continue"`: Continue processing. Apply the request modification, then pass the modified request to the next filter
+ listed; or if at the end of the list, forward it to the upstream service.
+
+### HTTPHeaderMatch
+
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of the HTTP Header to be matched. Only one of `value` or `valueRegex` can be configured |
+| `valueRegex` | `string` | Regex expression for matching the value of the HTTP Header. Only one of `value` or `valueRegex` can be configured |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### FilterArguments
+
+The Filter arguments fiels is an untyped map that allows for additional configuration specific to each filter to be provided.
+Refer to the usage guides for each filter type to see if it has any arguments that can be supplied.
+
+[FilterPolicyRule]: #filterpolicyrule
+[FilterReference]: #filterreference
+[FilterArguments]: #filterarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[Filter custom resource]: ../filter
+[the v1alpha1 FilterPolicy api reference]: ../../../gateway-getambassador/v1alpha1/filterpolicy
+[corresponding Kubernetes validation]: https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
diff --git a/docs/edge-stack/latest/doc-links.yml b/docs/edge-stack/latest/doc-links.yml
new file mode 100644
index 000000000..6d32f7e88
--- /dev/null
+++ b/docs/edge-stack/latest/doc-links.yml
@@ -0,0 +1,327 @@
+ - title: Quick start
+ link: /tutorials/getting-started
+ - title: Core concepts
+ items:
+ - title: Kubernetes network architecture
+ link: /topics/concepts/kubernetes-network-architecture
+ - title: 'The Ambassador operating model: GitOps and continuous delivery'
+ link: /topics/concepts/gitops-continuous-delivery
+ - title: Progressive delivery
+ link: /topics/concepts/progressive-delivery
+ - title: Microservices API gateways
+ link: /topics/concepts/microservices-api-gateways
+ - title: $productName$ architecture
+ link: /topics/concepts/architecture
+ - title: Rate limiting at the edge
+ link: /topics/concepts/rate-limiting-at-the-edge
+ - title: Installation and updates
+ link: /topics/install/
+ items:
+ - title: Install with Helm
+ link: /topics/install/helm
+ - title: Install with Kubernetes YAML
+ link: /topics/install/yaml-install
+ - title: Try the demo with Docker
+ link: /topics/install/docker
+ - title: Upgrade or migrate to a newer version
+ link: /topics/install/migration-matrix
+ - title: Edge Stack user guide
+ items:
+ - title: Deployment
+ items:
+ - title: Deployment architecture
+ link: /topics/running/ambassador-deployment
+ - title: $productName$ environment variables and ports
+ link: /topics/running/environment
+ - title: $productName$ and Redis
+ link: /topics/running/aes-redis
+ - title: $productName$ with AWS
+ link: /topics/running/ambassador-with-aws
+ - title: $productName$ with GKE
+ link: /topics/running/ambassador-with-gke
+ - title: Advanced deployment configuration
+ link: /topics/running/running
+ - title: Performance and scaling $productName$
+ link: /topics/running/scaling
+ - title: Active health checking configuration
+ link: /howtos/active-health-checking
+ - title: HTTP/3 configuration
+ items:
+ - title: HTTP3 setup in $productName$
+ link: /topics/running/http3
+ - title: HTTP/3 with AKS
+ link: /howtos/http3-aks
+ - title: HTTP/3 with EKS
+ link: /howtos/http3-eks
+ - title: HTTP/3 with GKE
+ link: /howtos/http3-gke
+ - title: Web Application Firewalls
+ items:
+ - title: $productName$'s Web Application Firewall
+ link: /howtos/web-application-firewalls
+ - title: Configuring Web Application Firewall rules
+ link: /howtos/web-application-firewalls-config
+ - title: Using Web Application Firewalls in Production
+ link: /howtos/web-application-firewalls-in-production
+ - title: Service routing and communication
+ items:
+ - title: Configuring $productName$ to communicate
+ link: /howtos/configure-communications
+ - title: Get traffic from the edge
+ link: /howtos/route
+ - title: TCP connections
+ link: /topics/using/tcpmappings
+ - title: gRPC connections
+ link: /howtos/grpc
+ - title: WebSocket connections
+ link: /howtos/websockets
+ - title: Authentication
+ items:
+ - title: Basic authentication
+ link: /howtos/ext-filters
+ - title: Using the OAuth2 filter for SSO
+ link: /howtos/oauth-oidc-auth
+ - title: Single Sign-On with Google
+ link: /howtos/sso/google
+ - title: Single Sign-On with Keycloak
+ link: /howtos/sso/keycloak
+ - title: Kubernetes SSO with OIDC and Keycloak
+ link: /howtos/auth-kubectl-keycloak
+ - title: Single Sign-On with Okta
+ link: /howtos/sso/okta
+ - title: Single Sign-On with Auth0
+ link: /howtos/sso/auth0
+ - title: Single Sign-On with Azure AD
+ link: /howtos/sso/azure
+ - title: Single Sign-On with OneLogin
+ link: /howtos/sso/onelogin
+ - title: Single Sign-On with Salesforce
+ link: /howtos/sso/salesforce
+ - title: Single Sign-On with UAA
+ link: /howtos/sso/uaa
+ - title: Authentication extension
+ link: /topics/running/aes-extensions/authentication
+ - title: Rate limiting
+ items:
+ - title: Rate limiting in $productName$
+ link: /howtos/advanced-rate-limiting
+ - title: Basic rate limiting
+ link: /topics/using/rate-limits/
+ - title: Rate limiting on token claims
+ link: /howtos/token-ratelimit
+ - title: Rate limiting reference
+ link: /topics/using/rate-limits/rate-limits
+ - title: Rate limiting extension
+ link: /topics/running/aes-extensions/ratelimit
+ - title: Service monitoring
+ items:
+ - title: Explore distributed tracing and Kubernetes monitoring
+ link: /howtos/dist-tracing
+ - title: Distributed tracing with Datadog
+ link: /howtos/tracing-datadog
+ - title: Distributed tracing with Zipkin
+ link: /howtos/tracing-zipkin
+ - title: Distributed tracing with LightStep
+ link: /howtos/tracing-lightstep
+ - title: Monitoring with Prometheus and Grafana
+ link: /howtos/prometheus
+ - title: Statistics
+ link: /topics/running/statistics
+ - title: Envoy statistics with StatsD
+ link: /topics/running/statistics/envoy-statsd
+ - title: The metrics endpoint
+ link: /topics/running/statistics/8877-metrics
+ - title: $productName$ integrations
+ items:
+ - title: Knative Serverless Framework
+ link: /howtos/knative
+ - title: ExternalDNS integration
+ link: /howtos/external-dns
+ - title: Consul integration
+ link: /howtos/consul
+ - title: Istio integration
+ link: /howtos/istio
+ - title: Linkerd 2 integration
+ link: /howtos/linkerd2
+ - title: Technical reference
+ items:
+ - title: Using Custom Resources
+ items:
+ - title: The Host resource
+ link: /topics/running/host-crd
+ - title: The Listener resource
+ link: /topics/running/listener
+ - title: The Module resource
+ link: /topics/running/ambassador
+ - title: The Mapping resource
+ link: /topics/using/intro-mappings
+ - title: Advanced Mapping configuration
+ link: /topics/using/mappings
+ - title: TLS configuration
+ items:
+ - title: TLS overview
+ link: /topics/running/tls/
+ - title: Cleartext support
+ link: /topics/running/tls/cleartext-redirection
+ - title: Mutual TLS (mTLS)
+ link: /topics/running/tls/mtls
+ - title: Server Name Indication (SNI)
+ link: /topics/running/tls/sni
+ - title: TLS origination
+ link: /topics/running/tls/origination
+ - title: TLS termination and enabling HTTPS
+ link: /howtos/tls-termination
+ - title: Using cert-manager
+ link: /howtos/cert-manager
+ - title: Client certificate validation
+ link: /howtos/client-cert-validation
+ - title: Filters
+ items:
+ - title: Using Filters and FilterPolicies
+ link: /topics/using/filters/
+ - title: Using OAuth2 Filters
+ link: /topics/using/filters/oauth2
+ - title: Using JWT Filters
+ link: /topics/using/filters/jwt
+ - title: Using External Filters
+ link: /topics/using/filters/external
+ - title: Using Plugin Filters
+ link: /topics/using/filters/plugin
+ - title: Using API Keys Filter
+ link: /topics/using/filters/apikeys
+ - title: Ingress and load balancing
+ items:
+ - title: AuthService settings
+ link: /topics/using/authservice
+ - title: Automatic retries
+ link: /topics/using/retries
+ - title: Canary releases
+ link: /topics/using/canary
+ - title: Circuit Breakers
+ link: /topics/using/circuit-breakers
+ - title: Cross-Origin Resource Sharing (CORS)
+ link: /topics/using/cors
+ - title: Ingress controller
+ link: /topics/running/ingress-controller
+ - title: Load balancing
+ link: /topics/running/load-balancer
+ - title: Service discovery and resolvers
+ link: /topics/running/resolvers
+ - title: Headers
+ items:
+ - title: Headers overview
+ link: /topics/using/headers/headers
+ - title: Add request headers
+ link: /topics/using/headers/add_request_headers
+ - title: Remove request headers
+ link: /topics/using/headers/remove_request_headers
+ - title: Add response headers
+ link: /topics/using/headers/add_response_headers
+ - title: Remove response headers
+ link: /topics/using/headers/remove_response_headers
+ - title: Header-based routing
+ link: /topics/using/headers/headers
+ - title: Host header
+ link: /topics/using/headers/host
+ - title: Routing
+ items:
+ - title: Keepalive
+ link: /topics/using/keepalive
+ - title: Method-based routing
+ link: /topics/using/method
+ - title: Prefix regex
+ link: /topics/using/prefix_regex
+ - title: Query parameter-based routing
+ link: /topics/using/query_parameters/
+ - title: Redirects
+ link: /topics/using/redirects
+ - title: Rewrites
+ link: /topics/using/rewrites
+ - title: Timeouts
+ link: /topics/using/timeouts
+ - title: Traffic shadowing
+ link: /topics/using/shadowing
+ - title: Plug-in services
+ items:
+ - title: Authentication service
+ link: /topics/running/services/auth-service
+ - title: ExtAuth protocol
+ link: /topics/running/services/ext_authz
+ - title: Log service
+ link: /topics/running/services/log-service
+ - title: Rate limit service
+ link: /topics/running/services/rate-limit-service
+ - title: Tracing service
+ link: /topics/running/services/tracing-service
+ - title: Traffic management
+ items:
+ - title: Custom error responses
+ link: /topics/running/custom-error-responses
+ - title: Gzip compression
+ link: /topics/running/gzip
+ - title: API
+ items:
+ - title: Gateway API
+ link: /topics/using/gateway-api
+ - title: Developer Portal
+ link: /topics/using/dev-portal
+ - title: CRD API References
+ items:
+ - title: getambassador.io/v3alpha1
+ items:
+ - title: Filter
+ link: /custom-resources/getambassador/v3alpha1/filter
+ items:
+ - title: The OAuth2 Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-oauth2
+ - title: The JWT Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-jwt
+ - title: The External Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-external
+ - title: The APIKey Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-apikey
+ - title: The Plugin Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-plugin
+ - title: FilterPolicy
+ link: /custom-resources/getambassador/v3alpha1/filterpolicy
+ - title: gateway.getambassador.io/v1alpha1
+ items:
+ - title: Filter
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter
+ items:
+ - title: The OAuth2 Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-oauth2
+ - title: The JWT Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-jwt
+ - title: The External Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-external
+ - title: The APIKey Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-apikey
+ - title: The Plugin Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-plugin
+ - title: FilterPolicy
+ link: /custom-resources/gateway-getambassador/v1alpha1/filterpolicy
+ - title: WebApplicationFirewall
+ link: /custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall
+ - title: WebApplicationFirewallPolicy
+ link: /custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy
+ - title: FAQs
+ link: /about/faq
+ - title: Troubleshooting
+ link: /topics/running/debugging
+ - title: Known issues
+ link: /about/known-issues
+ - title: Changes in $productName$ 2.X
+ link: /about/changes-2.x
+ - title: Changes in $productName$ 3.X
+ link: /about/changes-3.y
+ - title: Release Notes
+ link: /release-notes
+ - title: Community
+ link: /community
+ - title: End of Life Policy
+ link: /about/aes-emissary-eol
+ - title: $productName$ Licenses
+ link: topics/using/licenses
+ - title: Open Source Dependency Licenses
+ link: licenses
diff --git a/docs/edge-stack/latest/howtos/advanced-rate-limiting.md b/docs/edge-stack/latest/howtos/advanced-rate-limiting.md
new file mode 100644
index 000000000..491b71cd0
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/advanced-rate-limiting.md
@@ -0,0 +1,246 @@
+# Advanced rate limiting
+
+$productName$ features a built-in [Rate Limit Service (RLS)](../../topics/running/services/rate-limit-service/#external-rate-limit-service). The $productName$ RLS uses a decentralized configuration model that enables individual teams the ability to independently manage [rate limits](https://www.getambassador.io/kubernetes-glossary/rate-limiting) independently.
+
+All of the examples on this page use the backend service of the quote sample application to illustrate how to perform the rate limiting functions.
+
+## Rate Limiting in $productName$
+
+In $productName$, the `RateLimit` resource defines the policy for rate limiting. The rate limit policy is applied to individual requests according to the labels you add to the `Mapping` resource. This allows you to assign labels based on the particular needs of you rate limiting policies and apply the `RateLimit` policies to only the domains in the related `Mapping` resource.
+
+You can apply the `RateLimit` policy globally to all requests with matching labels from the `Module` resource. This can be used in conjunction with the `Mapping` resource to have a global rate limit with more granular rate limiting for specific requests that go through that specific `Mapping` resource.
+
+ In order for you to enact rate limiting policies:
+
+* Each domain you target needs to have labels.
+* For individual request, the service's `Mapping` resource needs to contain the labels related to the domains you want to apply the rate limiting policy to.
+* For global requests, the service's `Module` resource needs to contain the labels related to the policy you want to apply.
+* The `RateLimit` resource needs to set the rate limit policy for the labels the `Mapping` resource.
+
+
+## Rate limiting for availability
+
+Global rate limiting applies to the entire Kubernetes service mesh. This example shows how to limit the `quote` service to 3 requests per minute.
+
+1. First, add a request label to the `request_label_group` of the `quote` service's `Mapping` resource. This example uses `backend` for the label:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - generic_key:
+ value: backend
+ ```
+
+ Apply the mapping configuration changes with `kubectl apply -f quote-backend.yaml`.
+
+
+ You need to use v2 or later for the apiVersion in the Mapping resource. Previous versions do not support labels.
+
+
+2. Next, configure the `RateLimit` resource for the service. Create a new YAML file named `backend-ratelimit.yaml` and apply the rate limit details as follows:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{generic_key: backend}]
+ rate: 3
+ unit: minute
+ ```
+
+ In the code above, the `generic_key` is a hard-coded value that is used when you add a single string label to a request.
+
+3. Deploy the rate limit with `kubectl apply -f backend-ratelimit.yaml`.
+
+## Per user rate limiting
+
+Per user rate limiting enables you to apply the defined rate limit to specific IP addresses. To allow per user rate limits, you need to make sure you've properly configured $productName$ to [propagate your original client IP address](../../topics/running/ambassador/#trust-downstream-client-ip).
+
+This example shows how to use the `remote_address` special value in the mapping to target specific IP addresses:
+
+1. Add a request label to the `request_label_group` of the `quote` service's `Mapping` resource. This example uses `remote_address` for the label:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - remote_address:
+ key: remote_address
+ ```
+
+2. Update the rate limit amounts for the `RateLimit` service and enter the `remote_address` to the following pattern:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}]
+ rate: 3
+ unit: minute
+ ```
+
+## Load shedding
+
+Another technique for rate limiting involves load shedding. With load shedding, you can define which HTTP request method to allow or deny.
+
+This example shows how to implement load per user rate limiting along with load shedding on `GET` requests.
+To allow per user rate limits, you need to make sure you've properly configured $productName$ to [propagate your original client IP address](../../topics/running/ambassador#trust-downstream-client-ip).
+
+1. Add a request labels to the `request_label_group` of the `quote` service's `Mapping` resource. This example uses `remote_address` for the per user limit, and `backend_http_method`for load shedding. The load shedding uses `":method"` to identify that the `RateLimit` will use a HTTP request method in its pattern.
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - remote_address:
+ key: remote_address
+ - request_headers:
+ key: backend_http_method
+ header_name: ":method"
+ ```
+
+2. Update the rate limit amounts for the `RateLimit` service.
+For the rate limit `pattern`, include the `remote_address` IP address and the `backend_http_mthod`.
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}, {backend_http_method: GET}]
+ rate: 3
+ unit: minute
+ ```
+
+ When a pattern has multiple criteria, the rate limit runs when when any of the rules of the pattern match. For the example above, this means either a `remote_address` or `backend_http_method` pattern triggers the rate limiting.
+
+## Global rate limiting
+
+Similar to the per user rate limiting, you can use [global rate limiting](../../topics/using/rate-limits) to assign a rate limit to any unique IP addresses call to your service. Unlike the previous examples, you need to add your labels to the `Module` resource rather than the `Mapping` resource. This is because the `Module` resource applies the labels to all the requests in $productName$, whereas the labels in `Mapping` only apply to the requests that use that `Mapping` resource.
+
+1. Add a request label to the `request_label_group` of the `quote` service's `Module` resource. This example uses the `remote_address` special value.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Module
+ metadata:
+ name: ambassador
+ spec:
+ config:
+ use_remote_address: true
+ default_label_domain: ambassador
+ default_labels:
+ ambassador:
+ defaults:
+ - remote_address:
+ key: remote_address
+ ```
+2. Update the rate limit amounts for the `RateLimit` service and enter the `remote_address` to the following pattern:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: global-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}]
+ rate: 10
+ unit: minute
+ ```
+
+### Bypassing a global rate limit
+
+Sometimes, you may have an API that cannot handle as much load as others in your cluster. In this case, a global rate limit may not be enough to ensure this API is not overloaded with requests from a user. To protect this API, you can create a label that tells $productName$ to apply a stricter limit on requests.
+In the example above, the global rate limit is defined in the `Module` resource. This applies the limit to all requests. In conjunction with the global limit defined in the `Module` resource, you can add more granular rate limiting to a `Mapping` resource, which will only apply to requests that use that 'Mapping'.
+
+1. In addition to the configurations applied in the global rate limit example above, add an additional label to the `request_label_group` of the `Mapping` resource. This example uses `backend` for the label:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: quote-backend
+ spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - request_label_group:
+ - generic_key:
+ value: backend
+ ```
+
+2. Now, the `request_label_group` contains both the `generic_key: backend` and the `remote_address` key applied from the global rate limit. This creates a separate `RateLimit` object for this route:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: RateLimit
+ metadata:
+ name: backend-rate-limit
+ spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}, {generic_key: backend}]
+ rate: 3
+ unit: minute
+ ```
+
+ Requests to `/backend/` now are now limited after 3 requests. All other requests use the global rate limit policy.
+
+## Rate limit matching rules
+
+The following rules apply to the rate limit patterns:
+
+* Patterns are order-sensitive and must be entered in the same order in which a request is labeled.
+* Every label in a label group must exist in the pattern in order for matching to occur.
+* By default, any type of failure lets the request pass through (fail open).
+* $productName$ sets a hard timeout of 20ms on the rate limiting service. If the rate limit service does not respond within the timeout period, the request passes through.
+* If a pattern does not match, the request passes through.
+
+## Troubleshooting rate limiting
+
+The most common source of failure of the rate limiting service occurs when the labels generated by $productName$ do not match the rate limiting pattern. By default, the rate limiting service logs all incoming labels from $productName$. Use a tool such as [Stern](https://github.com/stern/stern) to watch the rate limiting logs from $productName$ and ensure the labels match your descriptor.
+
+## More
+
+For more on rate limiting, see the [rate limit guide](../../topics/using/rate-limits/).
diff --git a/docs/edge-stack/latest/howtos/auth-kubectl-keycloak.md b/docs/edge-stack/latest/howtos/auth-kubectl-keycloak.md
new file mode 100644
index 000000000..04996fd35
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/auth-kubectl-keycloak.md
@@ -0,0 +1,294 @@
+# Kubernetes SSO with OIDC and Keycloak
+
+Developers use `kubectl` to access Kubernetes clusters. By default `kubectl` uses a certificate to authenticate to the Kubernetes API. This means that when multiple developers need to access a cluster, the certificate needs to be shared. Sharing the credentials to access a Kubernetes cluster presents a significant security problem. Compromise of the certificate is very easy and the consequences can be catastrophic.
+
+In this tutorial, we walk through how to set up your Kubernetes cluster to add Single Sign-On support for `kubectl` using OpenID Connect (OIDC) and Keycloak. Instead of using a shared certificate, users will be able to use their own personal credentials to use `kubectl` with `kubelogin`.
+
+## Prerequisites
+
+This tutorial relies on $AESproductName$ to manage access to your Kubernetes cluster, and uses Keycloak as your identity provider. To get started:
+
+*Note* This guide was designed and validated using an Azure AKS Cluster. It's possible that this procedure will work with other cloud providers, but there is a lot of variance in the Authentication mechanisms for the Kubernetes API. See the troubleshooting note at the bottom for more info.
+
+* Azure AKS Cluster [here](https://docs.microsoft.com/en-us/azure/aks/tutorial-kubernetes-deploy-cluster)
+* Install $AESproductName$ [here](../../topics/install/)
+* Deploy Keycloak on Kubernetes [here](https://www.keycloak.org/getting-started/getting-started-kube)
+
+## Cluster Setup
+
+In this section, we'll configure your Kubernetes cluster for single-sign on.
+
+### 1. Authenticate $AESproductName$ with Kubernetes API
+
+1. Delete the openapi mapping from the Ambassador namespace `kubectl delete -n ambassador ambassador-devportal-api`. (this mapping can conflict with `kubectl` commands)
+
+2. Create a new private key using `openssl genrsa -out aes-key.pem 4096`.
+
+3. Create a file `aes-csr.cnf` and paste the following config.
+
+ ```cnf
+ [ req ]
+ default_bits = 2048
+ prompt = no
+ default_md = sha256
+ distinguished_name = dn
+
+ [ dn ]
+ CN = ambassador-kubeapi # Required
+
+ [ v3_ext ]
+ authorityKeyIdentifier=keyid,issuer:always
+ basicConstraints=CA:FALSE
+ keyUsage=keyEncipherment,dataEncipherment
+ extendedKeyUsage=serverAuth,clientAuth
+ ```
+
+4. Create a certificate signing request with the config file we just created. `openssl req -config ./aes-csr.cnf -new -key aes-key.pem -nodes -out aes-csr.csr`.
+
+5. Create and apply the following YAML for a CertificateSigningRequest. Replace {{BASE64_CSR}} with the value from `cat aes-csr.csr | base64`. Note that this is `aes-csr.csr`, and not `aes-csr.cnf`.
+
+ ```yaml
+ apiVersion: certificates.k8s.io/v1beta1
+ kind: CertificateSigningRequest
+ metadata:
+ name: aes-csr
+ spec:
+ groups:
+ - system:authenticated
+ request: {{BASE64_CSR}} # Base64 encoded aes-csr.csr
+ usages:
+ - digital signature
+ - key encipherment
+ - server auth
+ - client auth
+ ```
+
+6. Check csr was created: `kubectl get csr` (it will be in pending state). After confirmation, run `kubectl certificate approve aes-csr`. You can check `kubectl get csr` again to see that it's in the `Approved, Issued` state.
+
+7. Get the resulting certificate and put it into a pem file. `kubectl get csr aes-csr -o jsonpath='{.status.certificate}' | base64 -d > aes-cert.pem`.
+
+8. Create a TLS `Secret` using our private key and public certificate. `kubectl create secret tls -n ambassador aes-kubeapi --cert ./aes-cert.pem --key ./aes-key.pem`
+
+9. Create a `Mapping` and `TLSContext` for the Kube API.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: TLSContext
+ metadata:
+ name: aes-kubeapi-context
+ namespace: ambassador
+ spec:
+ hosts:
+ - "*"
+ secret: aes-kubeapi
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: aes-kubeapi-mapping
+ namespace: ambassador
+ spec:
+ hostname: "*"
+ prefix: /
+ allow_upgrade:
+ - spdy/3.1
+ service: https://kubernetes.default.svc
+ timeout_ms: 0
+ tls: aes-kubeapi-context
+ ```
+
+10. Create RBAC for the "aes-kubeapi" user by applying the following YAML.
+
+ ```yaml
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: aes-impersonator-role
+ rules:
+ - apiGroups: [""]
+ resources: ["users", "groups", "serviceaccounts"]
+ verbs: ["impersonate"]
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ name: aes-impersonator-rolebinding
+ subjects:
+ - apiGroup: rbac.authorization.k8s.io
+ kind: User
+ name: aes-kubeapi
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: aes-impersonator-role
+ ```
+
+As a quick check, you should be able to `curl https:///api` and get a response similar to the following:
+
+ ```json
+ {
+ "kind": "APIVersions",
+ "versions": [
+ "v1"
+ ],
+ "serverAddressByClientCIDRs": [
+ {
+ "clientCIDR": "0.0.0.0/0",
+ "serverAddress": "\"\":443"
+ }
+ ]
+ }%
+ ```
+
+### 2. Set up Keycloak config
+
+1. Create a new Realm and Client (e.g. ambassador, ambassador)
+2. Make sure that `http://localhost:8000` and `http://localhost:18000` are valid Redirect URIs
+3. Set access type to confidential and Save
+4. Go to the Credentials tab and note down the secret
+5. Go to the user tab and create a user with the first name "john"
+
+### 3. Create a ClusterRole and ClusterRoleBinding for the OIDC user "john"
+
+1. Add the following RBAC to create a user "john" that only allowed to perform `kubectl get services` in the cluster.
+
+ ```yaml
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ name: john-binding
+ subjects:
+ - kind: User
+ name: john
+ apiGroup: rbac.authorization.k8s.io
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: john-role
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: john-role
+ rules:
+ - apiGroups: [""]
+ resources: ["services"]
+ verbs: ["get", "list"]
+ ```
+
+2. Test the API again with the following 2 `curls`: `curl https:///api/v1/namespaces/default/services?limit=500 -H "Impersonate-User: "john"` and `curl https:///api/v1/namespaces/default/pods?limit=500 -H "Impersonate-User: "john"`. You will find that the first curl should succeeds and the second curl should fail with the following response.
+
+```json
+{
+ "kind": "Status",
+ "apiVersion": "v1",
+ "metadata": {
+
+ },
+ "status": "Failure",
+ "message": "pods is forbidden: User \"john\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
+ "reason": "Forbidden",
+ "details": {
+ "kind": "pods"
+ },
+ "code": 403
+}
+```
+
+### 4. Create a JWT filter to authenticate the user
+
+1. Create the following JWT `Filter` and `FilterPolicy` based on this template:
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: "kubeapi-jwt-filter"
+ namespace: "ambassador"
+ spec:
+ JWT:
+ jwksURI: https:///auth/realms//protocol/openid-connect/certs # If the keycloak instance is internal, you may want to use the internal k8s endpoint (e.g. http://keycloak.keycloak) instead of figuring out how to exclude JWKS requests from the FilterPolicy
+ injectRequestHeaders:
+ - name: "Impersonate-User" # Impersonate-User is mandatory, you can also add an Impersonate-Groups if you want to do group-based RBAC
+ value: "{{ .token.Claims.given_name }}" # This uses the first name we specified in the Keycloak user account
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: "kubeapi-filter-policy"
+ namespace: "ambassador"
+ spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: kubeapi-jwt-filter
+ ```
+
+## Client set up
+
+Now, we need to set up the client. Each user who needs to access the Kubernetes cluster will need to follow these steps.
+
+### 1. Install kubelogin
+
+1. Install [kubelogin](https://github.com/int128/kubelogin#getting-started). Kubelogin is a `kubectl` plugin that enables OpenID Connect login with `kubectl`.
+
+2. Edit your local Kubernetes config file (either `~/.kube/config`, or your `$KUBECONFIG` file) to include the following, making sure to replace the templated values.
+
+ ```yaml
+ apiVersion: v1
+ kind: Config
+ clusters:
+ - name: azure-ambassador
+ cluster:
+ server: https://
+ contexts:
+ - name: azure-ambassador-kube-api
+ context:
+ cluster: azure-ambassador
+ user: azure-ambassador
+ users:
+ - name: azure-ambassador
+ user:
+ exec:
+ apiVersion: client.authentication.k8s.io/v1beta1
+ command: kubectl
+ args:
+ - oidc-login
+ - get-token
+ - --oidc-issuer-url=https:///auth/realms/
+ - --oidc-client-id=
+ - --oidc-client-secret=
+ ```
+
+3. Switch to the context set above (in the example it's `azure-ambassador-kube-api`).
+
+4. Run `kubectl get svc`. This should open a browser page to the Keycloak login. Type in the credentials for "john" and, on success, return to the terminal to see the kubectl response. Congratulations, you've set up Single Sign-On with Kubernetes!
+
+5. Now try running `kubectl get pods`, and notice we get an `Error from server (Forbidden): pods is forbidden: User "john" cannot list resource "pods" in API group "" in the namespace "default"`. This is expected because we explicitly set up "john" to only have access to view `Service` resources, and not `Pods`.
+
+### 7. Logging Out
+
+1. Delete the token cache with `rm -r ~/.kube/cache/oidc-login`
+2. You may also have to remove session cookies in your browser or do a remote logout in the keycloak admin page.
+
+### Troubleshooting
+
+1. Why isn't this process working in my `` cluster?
+ Authentication to the Kubernetes API is highly cluster specific. Many use x509 certificates, but as a notable exception, Amazon's Elastic Kubernetes Service, for example, uses an Authenticating Webhook that connects to their IAM solution for Authentication, and so is not compatible specifically with this guide.
+2. What if I want to use RBAC Groups?
+ User impersonation allows you to specify a Group using the `Impersonate-Group` header. As such, if you wanted to use any kind of custom claims for the ID token, they can be mapped to the `Impersonate-Group` header. Note that you always have to use an `Impersonate-Name` header, even if you're relying solely on the Group for Authorization.
+3. I keep getting a 401 `Failure`, `Unauthorized` message, even for `https:///api`.
+ This likely means that there is either something wrong with the Certificate that was issued, or there's something wrong with your `TLSContext` or `Mapping` config. $AESproductName$ must present the correct certificate to the Kubernetes API and the RBAC usernames and the CN of the certificate have to be consistent with one another.
+4. Do I have to use `kubelogin`?
+ Technically no. Any method of obtaining an ID or Access token from an Identity Provider will work. You can then pass the token using `--token ` when running `kubectl`. `kubelogin` simply automates the process of getting the ID token and attaching it to a `kubectl` request.
+
+## Under the Hood
+
+In this tutorial, we set up $AESproductName$ to [impersonate a user](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation) to access the Kubernetes API. Requests get sent to $AESproductName$, which functions as an Authenticating Proxy. $AESproductName$ uses its integrated authentication mechanism to authenticate the external request's identity and sets the User and Group based on Claims recieved by the `Filter`.
+
+The general flow of the `kubectl` command is as follows: On making an unauthenticated kubectl command, `kubelogin` does a browser open/redirect in order to do OIDC token negotiation. `kubelogin` obtains an OIDC Identity Token (notice this is not an access token) and sends it to $AESproductName$ in an Authorization header. $AESproductName$ validates the Identity Token and parses Claims from it to put into `Impersonate-XXX` headers. $AESproductName$ then scrubs the Authorization header and replaces it with the Admin token we set up in step 1. $AESproductName$ then forwards this request with the new Authorization and Impersonate headers to the KubeAPI to first Authenticate, and then Authorize based on Kubernetes RBAC.
diff --git a/docs/edge-stack/latest/howtos/controlling-404.md b/docs/edge-stack/latest/howtos/controlling-404.md
new file mode 100644
index 000000000..5d8357fc5
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/controlling-404.md
@@ -0,0 +1,23 @@
+# Controlling the Edge Stack 404 Page
+
+Established users will want to better control 404 behavior both for usability and
+security. You can leverage the `Mapping` resource to implement this
+functionality to your cluster. $productName$ users can use a 'catch-all' mapping
+using the `/` prefix in a `Mapping` configuration. The simplest `Mapping`, described
+below, returns only 404 text. To use a custom 404 landing page, simply insert your
+service and remove the rewrite value.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: "404-fallback"
+spec:
+ hostname: "*"
+ prefix: "/"
+ rewrite: "/404/" # This must not map to any existing prefix!
+ service: localhost:8500 # This needs to exist, but _not_ respond on /404/
+```
+
+For more information on the `Mapping` resource, see
+[Advanced `Mapping` Configuration](../../topics/using/mappings).
diff --git a/docs/edge-stack/latest/howtos/ext-filters.md b/docs/edge-stack/latest/howtos/ext-filters.md
new file mode 100644
index 000000000..f7e5edc47
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/ext-filters.md
@@ -0,0 +1,208 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Basic authentication
+
+
+ This guide applies to $AESproductName$, use of this guide on the $OSSproductName$ is not recommended. API Gateway does authentication using the AuthService resource instead of the Filter resource as described below.
+
+
+$AESproductName$ can authenticate incoming requests before routing them to a backing
+service. In this tutorial, we'll configure $AESproductName$ to use an external third
+party authentication service. We're assuming also that you are running the
+quote application in your cluster as described in the
+[$AESproductName$ tutorial](../../tutorials/quickstart-demo/).
+
+## 1. Deploy the authentication service
+
+$AESproductName$ delegates the actual authentication logic to a third party authentication service. We've written a [simple authentication service](https://github.com/datawire/ambassador-auth-service) that:
+
+- listens for requests on port 3000;
+- expects all URLs to begin with `/extauth/`;
+- performs HTTP Basic Auth for all URLs starting with `/backend/get-quote/` (other URLs are always permitted);
+- accepts only user `username`, password `password`; and
+- makes sure that the `x-qotm-session` header is present, generating a new one if needed.
+
+$AESproductName$ routes _all_ requests through the authentication service: it relies on the auth service to distinguish between requests that need authentication and those that do not. If $AESproductName$ cannot contact the auth service, it will return a 503 for the request; as such, **it is very important to have the auth service running before configuring $AESproductName$ to use it.**
+
+Here's the YAML we'll start with:
+
+```yaml
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: example-auth
+spec:
+ type: ClusterIP
+ selector:
+ app: example-auth
+ ports:
+ - port: 3000
+ name: http-example-auth
+ targetPort: http-api
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: example-auth
+spec:
+ replicas: 1
+ strategy:
+ type: RollingUpdate
+ selector:
+ matchLabels:
+ app: example-auth
+ template:
+ metadata:
+ labels:
+ app: example-auth
+ spec:
+ containers:
+ - name: example-auth
+ image: docker.io/datawire/ambassador-auth-service:2.0.0
+ imagePullPolicy: Always
+ ports:
+ - name: http-api
+ containerPort: 3000
+ resources:
+ limits:
+ cpu: "0.1"
+ memory: 100Mi
+```
+
+Note that the cluster does not yet contain any $AESproductName$ AuthService definition. This is intentional: we want the service running before we tell $AESproductName$ about it.
+
+The YAML above is published at getambassador.io, so if you like, you can just do
+
+```
+kubectl apply -f https://app.getambassador.io/yaml/v2-docs/$ossVersion$/demo/demo-auth.yaml
+```
+
+to spin everything up. (Of course, you can also use a local file, if you prefer.)
+
+Wait for the pod to be running before continuing. The output of `kubectl get pods` should look something like
+
+```
+$ kubectl get pods
+NAME READY STATUS RESTARTS AGE
+example-auth-6c5855b98d-24clp 1/1 Running 0 4m
+```
+Note that the `READY` field says `1/1` which means the pod is up and running.
+
+## 2. Configure $AESproductName$ authentication
+
+Once the auth service is running, we need to tell $AESproductName$ about it. The easiest way to do that is to first map the `example-auth` service with the following `Filter`:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: authentication
+spec:
+ External:
+ auth_service: "example-auth:3000"
+ path_prefix: "/extauth"
+ allowed_request_headers:
+ - "x-qotm-session"
+ allowed_authorization_headers:
+ - "x-qotm-session"
+```
+
+This configuration tells $AESproductName$ about the `Filter`, notably that it needs the `/extauth` prefix, and that it's OK for it to pass back the `x-qotm-session` header. Note that `path_prefix` and `allowed_headers` are optional.
+
+Next you must apply the `Filter` to your desired hosts and paths using a `FilterPolicy`. The following would enable your `Filter` on requests to all hosts and paths (just remember that our authentication service is only configured to perform authentication on requests to `/backend/get-quote/`, see the [auth service's repo](https://github.com/datawire/ambassador-auth-service) for more information).
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: authentication
+spec:
+ rules:
+ - host: "*"
+ path: /*
+ filters:
+ - name: authentication
+```
+
+You can also apply the `Filter` only to specific hosts and/or paths, allowing you to only require authentication on certain routes. The following `FilterPolicy` would only run your `Filter` to requests to the `/backend/get-quote/` path:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: authentication
+spec:
+ rules:
+ - host: "*"
+ path: /backend/get-quote/
+ filters:
+ - name: authentication
+```
+
+If the auth service uses a framework like [Gorilla Toolkit](http://www.gorillatoolkit.org) which enforces strict slashes as HTTP path separators, it is possible to end up with an infinite redirect where the filter's framework redirects any request with non-conformant slashing. This would arise if the above example had ```path_prefix: "/extauth/"```, the filter would see a request for ```/extauth//backend/get-quote/``` which would then be redirected to ```/extauth/backend/get-quote/``` rather than actually be handled by the authentication handler. For this reason, remember that the full path of the incoming request including the leading slash, will be appended to ```path_prefix``` regardless of non-conformant slashing.
+
+## 3. Test authentication
+
+If we `curl` to a protected URL:
+
+```
+$ curl -Lv $AMBASSADORURL/backend/get-quote/
+```
+
+We get a 401 since we haven't authenticated.
+
+```
+* TCP_NODELAY set
+* Connected to 54.165.128.189 (54.165.128.189) port 32281 (#0)
+> GET /backend/get-quote/ HTTP/1.1
+> Host: 54.165.128.189:32281
+> User-Agent: curl/7.63.0
+> Accept: */*
+>
+< HTTP/1.1 401 Unauthorized
+< www-authenticate: Basic realm="Ambassador Realm"
+< content-length: 0
+< date: Thu, 23 May 2019 15:24:55 GMT
+< server: envoy
+<
+* Connection #0 to host 54.165.128.189 left intact
+```
+
+If we authenticate to the service, we will get a quote successfully:
+
+```
+$ curl -Lv -u username:password $AMBASSADORURL/backend/get-quote/
+
+* TCP_NODELAY set
+* Connected to 54.165.128.189 (54.165.128.189) port 32281 (#0)
+* Server auth using Basic with user 'username'
+> GET /backend/get-quote/ HTTP/1.1
+> Host: 54.165.128.189:32281
+> Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
+> User-Agent: curl/7.63.0
+> Accept: */*
+>
+< HTTP/1.1 200 OK
+< content-type: application/json
+< date: Thu, 23 May 2019 15:25:06 GMT
+< content-length: 172
+< x-envoy-upstream-service-time: 0
+< server: envoy
+<
+{
+ "server": "humble-blueberry-o2v493st",
+ "quote": "Nihilism gambles with lives, happiness, and even destiny itself!",
+ "time": "2019-05-23T15:25:06.544417902Z"
+* Connection #0 to host 54.165.128.189 left intact
+}
+```
+
+## What's next?
+
+* Get started with authentication by [installing $AESproductName$](../../tutorials/getting-started/).
+
+* For more details see the [`External` filter](../../topics/using/filters) documentation.
diff --git a/docs/edge-stack/latest/howtos/external-dns.md b/docs/edge-stack/latest/howtos/external-dns.md
new file mode 100644
index 000000000..fd75f1b47
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/external-dns.md
@@ -0,0 +1,126 @@
+import Alert from '@material-ui/lab/Alert';
+
+# ExternalDNS with $productName$
+
+[ExternalDNS](https://github.com/kubernetes-sigs/external-dns) configures your existing DNS provider to make Kubernetes resources discoverable via public DNS servers by getting resources from the Kubernetes API to create a list of DNS records.
+
+
+## Getting started
+
+### Prerequisites
+
+Before you begin, review [ExternalDNS repo's deployment instructions](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster) to get information about supported DNS providers and steps to setup ExternalDNS for your provider. Each DNS provider has its own required steps, as well as annotations, arguments, and permissions needed for the following configuration.
+
+
+### Installation
+
+Configuration for a `ServiceAccount`, `ClusterRole`, and `ClusterRoleBinding` is necessary for the ExternalDNS deployment to support compatibility with $productName$ and allow ExternalDNS to get hostnames from $productName$'s `Hosts`.
+
+The following configuration is an example configuring $productName$ - ExternalDNS integration with [AWS Route53](https://aws.amazon.com/route53/) as the DNS provider. Refer to the [ExternalDNS documentation](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster) for annotations and arguments for your DNS Provider.
+
+
+1. Create a YAML file named `externaldns-config.yaml`, and copy the following configuration into it:
+
+
+ Ensure that the apiGroups include "getambassador.io" following "networking.k8s.io", and that the resources include "hosts" after "ingresses".
+
+
+ ```yaml
+ ---
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: external-dns
+ annotations:
+ eks.amazonaws.com/role-arn: {ARN} # AWS ARN role
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: external-dns
+ rules:
+ - apiGroups: [""]
+ resources: ["services","endpoints","pods"]
+ verbs: ["get","watch","list"]
+ - apiGroups: ["extensions","networking.k8s.io", "getambassador.io"]
+ resources: ["ingresses", "hosts"]
+ verbs: ["get","watch","list"]
+ - apiGroups: [""]
+ resources: ["nodes"]
+ verbs: ["list","watch"]
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ name: external-dns-viewer
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: external-dns
+ subjects:
+ - kind: ServiceAccount
+ name: external-dns
+ namespace: default
+ ---
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: external-dns
+ spec:
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: external-dns
+ template:
+ metadata:
+ labels:
+ app: external-dns
+ annotations:
+ iam.amazonaws.com/role: {ARN} # AWS ARN role
+ spec:
+ serviceAccountName: external-dns
+ containers:
+ - name: external-dns
+ image: registry.opensource.zalan.do/teapot/external-dns:latest
+ args:
+ - --source=ambassador-host
+ - --domain-filter=example.net # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
+ - --provider=aws
+ - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
+ - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
+ - --registry=txt
+ - --txt-owner-id= {Hosted Zone ID} # Insert Route53 Hosted Zone ID here
+ ```
+
+2. Review the arguments section from the ExternalDNS deployment.
+
+ Configure or remove arguments to fit your needs. Additional arguments required for your DNS provider can be found by checking the [ExternalDNS repo's deployment instructions](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster).
+
+ * `--source=ambassador-host` - required across all DNS providers to tell ExternalDNS to look for hostnames in the $productName$ `Host` configurations.
+
+3. Apply the above config with the following command to deploy ExternalDNS to your cluster and configure support for $productName$:
+
+ ```shell
+ kubectl apply -f externaldns-ambassador.yaml
+ ```
+
+## Usage
+
+After you've applied the above configuration, ExternalDNS is ready to use. Configure a `Host` with the following annotation to allow ExternalDNS to get the IP address of your $productName$'s LoadBalancer and register it with your DNS provider:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: your-hostname
+ annotations:
+ external-dns.ambassador-service: edge-stack.ambassador
+spec:
+ acmeProvider:
+ authority: none
+ hostname: your-hostname.example.com
+```
+
+
+Victory! ExternalDNS is now running and configured to report $productName$'s IP and hostname with your DNS provider.
diff --git a/docs/edge-stack/latest/howtos/index.md b/docs/edge-stack/latest/howtos/index.md
new file mode 100644
index 000000000..7270d09e9
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/index.md
@@ -0,0 +1,36 @@
+# "How-to" guides
+
+These guides are designed to help users quickly accomplish common tasks. The guides assume a certain level of understanding of $productName$. Many of these guides are contributed by third parties; we welcome contributions via Pull Request at https://github.com/emissary-ingress/emissary.
+
+* Integrating with Service Mesh. $productName$ natively integrates with many service meshes.
+ * [HashiCorp Consul](consul)
+ * [Istio](istio)
+ * [Linkerd](linkerd2)
+* Distributed tracing. $productName$ natively supports a number of distributed tracing systems to enable developers to visualize request flow in microservice and service-oriented architectures.
+ * [Datadog](tracing-datadog)
+ * [Zipkin](tracing-zipkin)
+* Identity providers. $AESproductName$ integrates with a number of OAuth Identity Providers via OpenID Connect.
+ * [Auth0](sso/auth0)
+ * [Azure Active Directory](sso/azure)
+ * [Google Identity](sso/google)
+ * [Keycloak](sso/keycloak)
+ * [Okta](sso/okta)
+ * [Onelogin](sso/onelogin)
+ * [Salesforce](sso/salesforce)
+ * [UAA](sso/uaa)
+* Monitoring. $productName$ integrates with a number of different monitoring/metrics providers.
+ * [Prometheus](prometheus)
+* [Developing Custom Filters](filter-dev-guide)
+* Frameworks and Protocols. $productName$ supports a wide range of protocols and cloud-native frameworks.
+ * [gRPC](grpc)
+ * [Knative Serverless Framework](knative)
+ * [WebSockets](websockets)
+* Security. $productName$ supports a number of strategies for securing Kubernetes services.
+ * [Controlling the $productName$ 404 Page](controlling-404)
+ * [Protecting the Diagnostics Interface](protecting-diag-access)
+ * [HTTPS and TLS termination](tls-termination)
+ * [Certificate Manager](cert-manager) can be used to automatically obtain and renew TLS certificates; $AESproductName$ natively integrates this functionality.
+ * [Client Certificate Validation](client-cert-validation)
+ * [Basic Authentication](basic-auth) is a tutorial on how to use the external authentication API to code your own authentication service.
+ * [Rate Limiting in $productName$](advanced-rate-limiting)
+ * [Single Sign-On with OAuth and OpenID Connect](oauth-oidc-auth)
diff --git a/docs/edge-stack/latest/howtos/istio.md b/docs/edge-stack/latest/howtos/istio.md
new file mode 100644
index 000000000..4c54bd1a4
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/istio.md
@@ -0,0 +1,438 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Istio integration
+
+$productName$ and Istio: Edge Proxy and Service Mesh together in one. $productName$ is deployed at the edge of your network and routes incoming traffic to your internal services (aka "north-south" traffic). [Istio](https://istio.io/) is a service mesh for microservices, and is designed to add application-level Layer (L7) observability, routing, and resilience to service-to-service traffic (aka "east-west" traffic). Both Istio and $productName$ are built using [Envoy](https://www.envoyproxy.io).
+
+$productName$ and Istio can be deployed together on Kubernetes. In this configuration, $productName$ manages
+traditional edge functions such as authentication, TLS termination, and edge routing. Istio mediates communication
+from $productName$ to services, and communication between services.
+
+This allows the operator to have the best of both worlds: a high performance, modern edge service ($productName$) combined with a state-of-the-art service mesh (Istio). While Istio has introduced a [Gateway](https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/) abstraction, $productName$ still has a much broader feature set for edge routing than Istio. For more on this topic, see our blog post on [API Gateway vs Service Mesh](https://blog.getambassador.io/api-gateway-vs-service-mesh-104c01fa4784).
+
+This guide explains how to take advantage of both $productName$ and Istio to have complete control and observability over how requests are made in your cluster:
+
+- [Install Istio](#install-istio) and configure auto-injection
+- [Install $productName$ with Istio integration](#install-edge)
+- [Configure an mTLS `TLSContext`](#configure-an-mtls-tlscontext)
+- [Route to services using mTLS](#route-to-services-using-mtls)
+
+If desired, you may also
+
+- [Enable strict mTLS](#enable-strict-mtls)
+- [Configure Prometheus metrics collection](#configure-prometheus-metrics-collection)
+- [Configure Istio distributed tracing](#configure-istio-distributed-tracing)
+
+To follow this guide, you need:
+
+- A Kubernetes cluster version 1.15 and above
+- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
+- Istio version 1.10 or higher
+
+## Install Istio
+
+Start by [installing Istio](https://istio.io/docs/setup/getting-started/). Any supported installation method for
+Istio will work for use with $productName$.
+
+### Configure Istio Auto-Injection
+
+Istio functions by supplying a sidecar container running Envoy with every service in the mesh (including
+$productName$). The sidecar is what enforces Istio policies for traffic to and from the service, notably
+including mTLS encryption and certificate handling. As such, it is very important that the sidecar be
+correctly supplied for every service in the mesh!
+
+While it is possible to manage sidecars by hand, it is far easier to allow Istio to automatically inject
+the sidecar as necessary. To do this, set the `istio-injection` label on each Kubernetes Namespace for
+which you want auto-injection:
+
+```yaml
+kubectl label namespace $namespace istio-injection=enabled --overwrite
+```
+
+
+ The following example uses the `istio-injection` label to arrange for auto-injection in the
+ `$productNamespace$` Namespace below. You can manage sidecar injection by hand if you wish; what
+ is critical is that every service that participates in the Istio mesh have the Istio
+ sidecar.
+
+
+## Install $productName$ with Istio Integration
+
+Properly integrating $productName$ with Istio provides support for:
+
+* [Mutual TLS (mTLS)](../../topics/running/tls/mtls), with certificates managed by Istio, to allow end-to-end encryption
+for east-west traffic;
+* Automatic generation of Prometheus metrics for services; and
+* Istio distributed tracing for end-to-end observability.
+
+The simplest way to enable everything is to install $productName$ using [Helm](https://helm.sh), though
+you can use manual installation with YAML if you wish.
+
+### Installation with Helm (Recommended)
+
+To install with Helm, write the following YAML to a file called `istio-integration.yaml`:
+
+```yaml
+# All of the values we need to customize live under the emissary-ingress toplevel key.
+emissary-ingress:
+ # Listeners are required in $productName$ 2.0.
+ # This will create the two default Listeners for HTTP on port 8080 and HTTPS on port 8443.
+ createDefaultListeners: true
+
+ # These are annotations that will be added to the $productName$ pods.
+ podAnnotations:
+ # These first two annotations tell Istio not to try to do port management for the
+ # $productName$ pod itself. Though these annotations are placed on the $productName$
+ # pods, they are interpreted by Istio.
+ traffic.sidecar.istio.io/includeInboundPorts: "" # do not intercept any inbound ports
+ traffic.sidecar.istio.io/includeOutboundIPRanges: "" # do not intercept any outbound traffic
+
+ # We use proxy.istio.io/config to tell the Istio proxy to write newly-generated mTLS certificates
+ # into /etc/istio-certs, which will be mounted below. Though this annotation is placed on the
+ # $productName$ pods, it is interpreted by Istio.
+ proxy.istio.io/config: |
+ proxyMetadata:
+ OUTPUT_CERTS: /etc/istio-certs
+
+ # We use sidecar.istio.io/userVolumeMount to tell the Istio sidecars to mount the istio-certs
+ # volume at /etc/istio-certs, allowing the sidecars to see the generated certificates. Though
+ # this annotation is placed on the $productName$ pods, it is interpreted by Istio.
+ sidecar.istio.io/userVolumeMount: '[{"name": "istio-certs", "mountPath": "/etc/istio-certs"}]'
+
+ # We define a single storage volume called "istio-certs". It starts out empty, and Istio
+ # uses it to communicate mTLS certs between the Istio proxy and the Istio sidecars (see the
+ # annotations above).
+ volumes:
+ - emptyDir:
+ medium: Memory
+ name: istio-certs
+
+ # We also tell $productName$ to mount the "istio-certs" volume at /etc/istio-certs in the
+ # $productName$ pod. This gives $productName$ access to the mTLS certificates, too.
+ volumeMounts:
+ - name: istio-certs
+ mountPath: /etc/istio-certs/
+ readOnly: true
+
+ # Finally, we need to set some environment variables for $productName$.
+ env:
+ # AMBASSADOR_ISTIO_SECRET_DIR tells $productName$ to look for Istio mTLS certs, and to
+ # make them available as a secret named "istio-certs".
+ AMBASSADOR_ISTIO_SECRET_DIR: "/etc/istio-certs"
+
+ # AMBASSADOR_ENVOY_BASE_ID is set to prevent collisions with the Istio sidecar's Envoy,
+ # which runs with base-id 0.
+ AMBASSADOR_ENVOY_BASE_ID: "1"
+```
+
+To install $productName$ with Helm, use these values to configure Istio integration:
+
+1. Install $productName$ if you are not already running it by [following the quickstart](../../tutorials/getting-started):
+
+2. Enable Istio auto-injection for $productName$'s namespace:
+
+ ```bash
+ kubectl label namespace $productNamespace$ istio-injection=enabled --overwrite
+ ```
+
+3. Use Helm to configure $productName$'s Istio integration
+
+4. Use Helm to install $productName$ in $productNamespace$:
+
+ ```bash
+ helm upgrade $productHelmName$ --namespace $productNamespace$ -f istio-integration.yaml datawire/$productHelmName$ && \
+ kubectl -n $productNamespace$ wait --for condition=available --timeout=90s deploy -lapp.kubernetes.io/instance=$productDeploymentName$
+ ```
+
+### Installation Using YAML
+
+If you are not using Helm to manage your $productName$ installation, you need to manually incorporate the contents of the `istio-integration.yaml` file shown above into your deployment YAML:
+
+- `pod-annotations` should be configured as Kubernetes `annotations` on the $productName$ Pods;
+- `volumes`, `volumeMounts`, and `env` contents should be included in the $productDeploymentName$ Deployment; and
+- you must also label the $productNamespace$ Namespace for auto-injection as described above.
+
+### Configuring an Existing Installation
+
+If you have already installed $productName$ and want to enable Istio:
+
+1. Install Istio.
+2. Label the $productNamespace$ namespace for Istio auto-injection, as above.
+3. Edit the $productName$ Deployments to contain the `annotations`, `volumes`, `volumeMounts`, and `env` elements
+ shown above.
+ - If you installed with Helm, you can use `helm upgrade` with `-f istio-integration.yaml` to modify the
+ installation for you.
+4. Restart the $productName$ pods.
+
+## Configure an mTLS `TLSContext`
+
+After configuring $productName$ for Istio integration, the Istio mTLS certificates are available within
+$productName$:
+
+- Both the `istio-proxy` sidecar and $productName$ mount the `istio-certs` volume at `/etc/istio-certs`.
+- The `istio-proxy` sidecar saves the mTLS certificates into `/etc/istio-certs` (per the `OUTPUT_CERTS`
+ environment variable).
+- $productName$ reads the mTLS certificates from `/etc/istio-certs` (per the `AMBASSADOR_ISTIO_SECRET_DIR`
+ environment variable) and creates a Secret named `istio-certs`.
+
+
+ At present, the Secret name istio-certs cannot be changed.
+
+
+To make use of the `istio-certs` Secret, create a `TLSContext` referencing it:
+
+ ```yaml
+ kubectl apply -f - <
+ You must either explicitly specify port 80 in your Mapping's service
+ element, or set up the Kubernetes Service resource for your upstream service to map port
+ 443. If you don't do one of these, connections to your upstream will hang — see the
+ "Configure Service Ports" section below for more information.
+
+
+The behavior of your service will not seem to change, even though mTLS is active:
+
+ ```console
+ $ curl -k https://{{AMBASSADOR_HOST}}/backend/
+
+ {
+ "server": "bewitched-acai-5jq7q81r",
+ "quote": "A late night does not make any sense.",
+ "time": "2020-06-02T10:48:45.211178139Z"
+ }
+ ```
+
+This request first went to $productName$, which routed it over an mTLS connection to the quote service in the
+default namespace. That connection was intercepted by the `istio-proxy` which authenticated the request as
+being from $productName$, exported various metrics, and finally forwarded it on to the actual quote service.
+
+### Configure Service Ports
+
+When mTLS is active, Istio makes TLS connections to your services. Since Istio handles the TLS protocol for
+you, you don't need to modify your services — however, the TLS connection will still use port 443
+if you don't configure your `Mapping`s to _explicitly_ use port 80.
+
+If your upstream service was not written to use TLS, its `Service` resource may only map port 80. If Istio
+attempts a TLS connection on port 443 when port 443 is not defined by the `Service` resource, the connection
+will hang _even though the Istio sidecar is active_, because Kubernetes itself doesn't know how to handle
+the connection to port 443.
+
+As shown above, one simple way to deal with this situation is to explicitly specify port 80 in the `Mapping`'s
+`service`:
+
+ ```yaml
+ service: quote:80 # Be explicit about port 80.
+ ```
+
+Another way is to set up your Kubernetes `Service` to map both port 80 and port 443. For example, the
+Quote deployment (which listens on port 8080 in its pod) might use a `Service` like this:
+
+ ```yaml
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: quote
+ spec:
+ type: ClusterIP
+ selector:
+ app: quote
+ ports:
+ - name: http
+ port: 80
+ protocol: TCP
+ targetPort: 8080
+ - name: https
+ port: 443
+ protocol: TCP
+ targetPort: 8080
+ ```
+
+Note that ports 80 and 443 are both mapped to `targetPort` 8080, where the service is actually listening.
+This permits Istio routing to work whether mTLS is active or not.
+
+## Enable Strict mTLS
+
+Istio defaults to _permissive_ mTLS, where mTLS is allowed between services, but not required. Configuring
+[_strict_ mTLS](https://istio.io/docs/tasks/security/authentication/authn-policy/#globally-enabling-istio-mutual-tls-in-strict-mode) requires all connections within the cluster be encrypted. To switch Istio to use strict mTLS,
+apply a `PeerAuthentication` resource in each namespace that should operate in strict mode:
+
+ ```yaml
+ $ kubectl apply -f - <
+ secret:
+ protectedOrigins:
+ - origin: http://domain1.example.com
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: domain2-tenant
+spec:
+ OAuth2:
+ authorizationURL: https://example.auth0.com
+ extraAuthorizationParameters:
+ audience: https://example.auth0.com/api/v2/
+ clientId:
+ secret:
+ protectedOrigins:
+ - origin: http://domain2.example.com
+```
+
+Create a separate `FilterPolicy` that specifies which specific filters are applied to particular hosts or URLs.
+
+## Further reading
+
+The [filter reference](../../topics/using/filters/) covers the specifics of filters and filter policies in much more detail.
diff --git a/docs/edge-stack/latest/howtos/sso/auth0.md b/docs/edge-stack/latest/howtos/sso/auth0.md
new file mode 100644
index 000000000..2d5903f9e
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/auth0.md
@@ -0,0 +1,75 @@
+# Single Sign-On with Auth0
+
+With Auth0 as your IdP, you will need to create an `Application` to handle authentication requests from $AESproductName$.
+
+1. Navigate to Applications and Select "CREATE APPLICATION"
+
+ 
+
+2. In the pop-up window, give the application a name and create a "Machine to Machine App"
+
+ 
+
+3. Select the Auth0 Management API. Grant any scope values you may
+ require. (You may grant none.) The API is required so that an
+ `audience` can be specified which will result in a JWT being
+ returned rather than opaque token. A custom API can also be used.
+
+ 
+
+4. In your newly created application, click on the Settings tab, add the Domain and Callback URLs for your service and ensure the "Token Endpoint Authentication Method" is set to `Post`. The default YAML installation of $AESproductName$ uses `/.ambassador/oauth2/redirection-endpoint` for the URL, so the values should be the domain name that points to $AESproductName$, e.g., `example.com/.ambassador/oauth2/redirection-endpoint` and `example.com`.
+
+ 
+
+ Click Advanced Settings > Grant Types and check "Authorization Code"
+
+## Configure Filter and FilterPolicy
+
+Update the Auth0 `Filter` and `FilterPolicy`. You can get the `ClientID` and `secret` from your application settings:
+
+
+ 
+
+ The `audience` is the API Audience of your Auth0 Management API:
+
+ 
+
+ The `authorizationURL` is your Auth0 tenant URL.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: auth0-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: https://datawire-ambassador.auth0.com
+ extraAuthorizationParameters:
+ audience: https://datawire-ambassador.auth0.com/api/v2/
+ clientID: fCRAI7svzesD6p8Pv22wezyYXNg80Ho8
+ secret: CLIENT_SECRET
+ protectedOrigins:
+ - origin: https://datawire-ambassador.com
+ ```
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: auth0-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "openid"
+ ```
+
+ **Note:** By default, Auth0 requires the `openid` scope.
diff --git a/docs/edge-stack/latest/howtos/sso/azure.md b/docs/edge-stack/latest/howtos/sso/azure.md
new file mode 100644
index 000000000..5e0fc071b
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/azure.md
@@ -0,0 +1,72 @@
+# Single Sign-On with Azure Active Directory (AD)
+
+## Set up Azure AD
+
+To use Azure as your IdP, you will first need to register an OAuth application with your Azure tenant.
+
+1. Follow the steps in the Azure documentation [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) to register your application. Make sure to select "web application" (not native application) when creating your OAuth application.
+
+2. After you have registered your application, click on `App Registrations` in the navigation panel on the left and select the application you just created.
+
+3. Make a note of both the client and tenant IDs as these will be used later when configuring $AESproductName$.
+
+4. Click on `Authentication` in the left sidebar.
+
+ - Under the `Platform configurations` section, click on `+ Add a platform`, then select `Web` and add this URL `https://{{AMBASSADOR_URL}}/.ambassador/oauth2/redirection-endpoint` into the `Redirect URIs` input field
+
+ **Note:** Azure AD requires the redirect endpoint to handle TLS
+ - Make sure your application is issuing `access tokens` by clicking on the `Access tokens (used for implicit flows)` checkbox under the `Implicit grant and hybrid flows` section
+ - Finally, click on `Configure` to save your changes
+
+5. Click on `Certificates & secrets` in the left sidebar. Click `+ New client secret` and set the expiration date you wish. Copy the value of this secret somewhere. You will need it when configuring $AESproductName$.
+
+## Set Up $AESproductName$
+
+After configuring an OAuth application in Azure AD, configuring $AESproductName$ to make use of it for authentication is simple.
+
+1. Create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials from above:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: azure-ad
+ spec:
+ OAuth2:
+ # Azure AD openid-configuration endpoint can be found at https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
+ authorizationURL: https://login.microsoftonline.com/{{TENANT_ID}}/v2.0
+ # Client ID from step 3 above
+ clientID: CLIENT_ID
+ # Secret created in step 5 above
+ secret: CLIENT_SECRET
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: https://{{AMBASSADOR_URL}}
+ ```
+
+2. Create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` created above
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: azure-policy
+ spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the quote /backend/get-quote/ endpoint
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: azure-ad
+ ```
+
+3. Apply both the `Filter` and `FilterPolicy` above with `kubectl`
+
+ ```
+ kubectl apply -f azure-ad-filter.yaml
+ kubectl apply -f azure-policy.yaml
+ ```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from Azure AD.
diff --git a/docs/edge-stack/latest/howtos/sso/google.md b/docs/edge-stack/latest/howtos/sso/google.md
new file mode 100644
index 000000000..d16f91517
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/google.md
@@ -0,0 +1,65 @@
+# Single Sign-On with Google
+
+## Create an OAuth client in the Google API Console
+
+To use Google as an IdP for Single Sign-On, you will first need to create an OAuth web application in the Google API Console.
+
+1. Open the [Credentials page](https://console.developers.google.com/apis/credentials) in the API Console
+2. Click `Create credentials > OAuth client ID`.
+3. Select `Web application` and give it a name
+4. Under **Restrictions**, fill in the **Authorized redirect URIs** with
+
+ ```
+ http(s)://{{AMBASSADOR_URL}}/.ambassador/oauth2/redirection-endpoint
+ ```
+5. Click `Create`
+6. Record the `client ID` and `client secret` in the pop-up window. You will need these when configuring $AESproductName$
+
+## Set up $AESproductName$
+
+After creating an OAuth client in Google, configuring $AESproductName$ to make use of it for authentication is simple.
+
+1. Create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials from above:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: google
+ spec:
+ OAuth2:
+ # Google openid-configuration endpoint can be found at https://accounts.google.com/.well-known/openid-configuration
+ authorizationURL: https://accounts.google.com
+ # Client ID from step 6 above
+ clientID: CLIENT_ID
+ # Secret created in step 6 above
+ secret: CLIENT_SECRET
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: http(s)://{{AMBASSADOR_URL}}
+ ```
+2. Create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` created above
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: google-policy
+ spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the quote /backend/get-quote/ endpoint
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: google
+ ```
+3. Apply both the `Filter` and `FilterPolicy` above with `kubectl`
+
+ ```
+ kubectl apply -f google-filter.yaml
+ kubectl apply -f google-policy.yaml
+ ```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from Google.
diff --git a/docs/edge-stack/latest/howtos/sso/keycloak.md b/docs/edge-stack/latest/howtos/sso/keycloak.md
new file mode 100644
index 000000000..4e55cc8bc
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/keycloak.md
@@ -0,0 +1,72 @@
+# Single Sign-On with Keycloak
+
+With Keycloak as your IdP, you will need to create a `Client` to handle authentication requests from $AESproductName$. The below instructions are known to work for Keycloak 4.8.
+
+1. Under "Realm Settings", record the "Name" of the realm your client is in. This will be needed to configure your `authorizationURL`.
+2. Create a new client: navigate to Clients and select `Create`. Use the following settings:
+ - Client ID: Any value (e.g. `ambassador`); this value will be used in the `clientID` field of the Keycloak filter
+ - Client Protocol: "openid-connect"
+ - Root URL: Leave Blank
+
+3. Click Save.
+
+4. On the next screen configure the following options:
+ - Access Type: "confidential"
+ - Valid Redirect URIs: `*`
+
+5. Click Save.
+6. Navigate to the `Mappers` tab in your Client and click `Create`.
+7. Configure the following options:
+ - Protocol: "openid-connect".
+ - Name: Any string. This is just a name for the Mapper
+ - Mapper Type: select "Audience"
+ - Included Client Audience: select from the dropdown the name of your Client. This will be used as the `audience` in the Keycloak `Filter`.
+
+8. Click Save.
+
+9. Configure client scope as desired in "Client Scopes"
+ (e.g. `offline_access`). It's possible to set up Keycloak to not
+ use scope by removing all of them from "Assigned Default Client
+ Scopes".
+
+ **Note:** All "Assigned Default Client Scopes" must be included in
+ the `FilterPolicy` `scope` argument.
+
+## Configure Filter and FilterPolicy
+
+Update the Keycloak `Filter` and `FilterPolicy` with the following:
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: keycloak-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: https://{KEYCLOAK_URL}/auth/realms/{KEYCLOAK_REALM}
+ audience: ambassador
+ clientID: ambassador
+ secret: {CLIENT_SECRET}
+ protectedOrigins:
+ - origin: https://{PROTECTED_URL}
+ ```
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: keycloak-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "offline_access"
+ ```
diff --git a/docs/edge-stack/latest/howtos/sso/okta.md b/docs/edge-stack/latest/howtos/sso/okta.md
new file mode 100644
index 000000000..f0735012a
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/okta.md
@@ -0,0 +1,64 @@
+# Single Sign-On with Okta
+
+1. Create an OIDC application
+
+ **Note:** If you have a [standard Okta account](https://www.okta.com) you must first navigate to your Okta Org's admin portal (step 1). [Developer accounts](https://developer.okta.com) can skip to Step 2.
+
+ - Go to your org and click `Admin` in the top right corner to access the admin portal
+ - Select `Applications`
+ - Select `Add Application`
+ - Choose `Web` and `OpenID Connect`. Then click `Create`.
+ - Give it a name, enter the URL of your $AESproductName$ load balancer in `Base URIs` and the callback URL `{AMBASSADOR_URL}/.ambassador/oauth2/redirection-endpoint` as the `Login redirect URIs`
+
+2. Copy the `Client ID` and `Client Secret` and use them to fill in the `ClientID` and `Secret` of you Okta OAuth `Filter`.
+
+3. Get the `audience` configuration
+
+ - Select `API` and `Authorization Servers`
+ - You can use the default `Authorization Server` or create your own.
+ - If you are using the default, the `audience` of your Okta OAuth `Filter` is `api://default`
+ - The value of the `authorizationURL` is the `Issuer URI` of the `Authorization Server`
+
+## Configure Filter and FilterPolicy
+
+Configure your OAuth `Filter` and `FilterPolicy` with the following:
+
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: okta-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: https://{OKTA_DOMAIN}.okta.com/oauth2/default
+ audience: api://default
+ clientID: CLIENT_ID
+ secret: CLIENT_SECRET
+ protectedOrigins:
+ - origin: https://datawire-ambassador.com
+ ```
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: okta-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "openid"
+ - "profile"
+ ```
+
+**Note:** Scope values `openid` and `profile` are required at a
+minimum. Other scope values can be added to the `Authorization Server`.
diff --git a/docs/edge-stack/latest/howtos/sso/onelogin.md b/docs/edge-stack/latest/howtos/sso/onelogin.md
new file mode 100644
index 000000000..59d318803
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/onelogin.md
@@ -0,0 +1,93 @@
+# Single Sign-On with OneLogin
+
+OneLogin is an application that manages authentication for your users on your network, and can provide backend access to $AESproductName$.
+
+To use OneLogin with $AESproductName$:
+
+1. Create an App Connector
+2. Gather OneLogin Credentials
+3. Configure $AESproductName$
+
+## Create an App Connector
+
+To use OneLogin as your IdP, you will first need to create an OIDC custom connector and create an application from that connector.
+
+**To do so**:
+
+1. In your OneLogin portal, select **Administration** from the top right.
+2. From the top left menu, select **Applications > Custom Connectors** and click the **New Connector** button.
+3. Give your connector a name.
+4. Select the `OpenID Connect` option as your "Sign on method."
+5. Use `http(s)://{{AMBASSADOR_URL/.ambassador/oauth2/redirection-endpoint` as the value for "Redirect URI."
+6. Optionally provide a login URL.
+7. Click the **Save** button to create the connector. You will see a confirmation message.
+8. In the "More Actions" tab, select **Add App to Connector**.
+9. Select the connector you just created.
+10. Click the **Save** button.
+
+You will see a success banner, which also brings you back to the main portal page. OneLogin is now configured to function as an OIDC backend for authentication with $AESproductName$.
+
+## Gather OneLogin Credentials
+
+Next, configure $AESproductName$ to require authentication with OneLogin, so you must collect the client information credentials from the application you just created.
+
+**To do so:**
+
+1. In your OneLogin portal, go to **Administration > Applications > Applications.**
+2. Select the application you previously created.
+3. On the left, select the **SSO** tab to see the client information.
+4. Copy the value of Client ID for later use.
+5. Click the **Show Client Secret** link and copy the value for later use.
+
+## Configure $AESproductName$
+
+Now you must configure your $AESproductName$ instance to use OneLogin.
+
+1. First, create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials you copied earlier.
+
+Here is an example YAML:
+
+```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: onelogin
+ spec:
+ OAuth2:
+ # onelogin openid-configuration endpoint can be found at https://{{subdomain}}.onelogin.com/oidc/.well-known/openid-configuration
+ authorizationURL: https://{{subdomain}}.onelogin.com/oidc
+ clientID: {{Client ID}}
+ secret: {{Client Secret}}
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: httpi(s)://{{AMBASSADOR_URL}}
+```
+
+2. Next, create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` you just created.
+
+Some example YAML:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: oauth-policy
+spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the /backend/get-quote/ endpoint from the quote application
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: onelogin
+```
+
+3. Lastly, apply both the `Filter` and `FilterPolicy` you created with a `kubectl` command in your terminal:
+
+```
+kubectl apply -f onelogin-filter.yaml
+kubectl apply -f oauth-policy.yaml
+```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from OneLogin.
diff --git a/docs/edge-stack/latest/howtos/sso/salesforce.md b/docs/edge-stack/latest/howtos/sso/salesforce.md
new file mode 100644
index 000000000..1410a4a42
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/salesforce.md
@@ -0,0 +1,79 @@
+# Single Sign-On with Salesforce
+
+## Set up Salesforce
+
+To use Salesforce as your IdP, you will first need to register an OAuth application with your Salesforce tenant. This guide will walk you through the most basic setup via the "Salesforce Classic Experience".
+
+1. In the `Setup` page, under `Build` click the dropdown next to `Create` and select `Apps`.
+2. Under `Connected Apps` at the bottom of the page, click on `New` at the top.
+3. Fill in the following fields with whichever values you want:
+
+ - Connected App Name
+ - API Name
+ - Contact Email
+
+4. Under `API (Enable OAuth Settings)` check the box next to `Enable OAuth Settings`.
+5. Fill in the `Callback URL` section with `https://{{AMBASSADOR_HOST}}/.ambassador/oauth2/redirection-endpoint`.
+6. Under `Selected OAuth Scopes` you must select the `openid` scope
+ value at the minimum. Select any other scope values you want to
+ include in the response as well.
+7. Click `Save` and `Continue` to create the application.
+8. Record the `Consumer Key` and `Consumer Secret` values from the `API (Enable OAuth Settings)` section in the newly created application's description page.
+
+After waiting for salesforce to register the application with their servers, you should be ready to configure $AESproductName$ to Salesforce as an IdP.
+
+## Set up $AESproductName$
+
+After configuring an OAuth application in Salesforce, configuring $AESproductName$ to make use of it for authentication is simple.
+
+1. Create an [OAuth Filter](../../../topics/using/filters/oauth2) with the credentials from above:
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: salesforce
+ spec:
+ OAuth2:
+ # Salesforce's generic OpenID configuration endpoint at https://login.salesforce.com/ will work but you can also use your custom Salesforce domain i.e.: http://datawire.my.salesforce.com
+ authorizationURL: https://login.salesforce.com/
+ # Consumer Key from above
+ clientID: {{Consumer Key}}
+ # Consumer Secret from above
+ secret: {{Consumer Secret}}
+ # The protectedOrigin is the scheme and Host of your $AESproductName$ endpoint
+ protectedOrigins:
+ - origin: https://{{AMBASSADOR_HOST}}
+ ```
+
+2. Create a [FilterPolicy](../../../topics/using/filters/) to use the `Filter` created above
+
+ ```yaml
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: oauth-policy
+ spec:
+ rules:
+ # Requires authentication on requests from any hostname
+ - host: "*"
+ # Tells $AESproductName$ to apply the Filter only on request to the quote /backend/get-quote/ endpoint
+ path: /backend/get-quote/
+ # Identifies which Filter to use for the path and host above
+ filters:
+ - name: salesforce
+ # Any additional scope values granted in step 6 above can be requested with the arguments field
+ # arguments:
+ # scope:
+ # - refresh_token
+
+ ```
+
+3. Apply both the `Filter` and `FilterPolicy` above with `kubectl`
+
+ ```
+ kubectl apply -f salesforce-filter.yaml
+ kubectl apply -f oauth-policy.yaml
+ ```
+
+Now any requests to `https://{{AMBASSADOR_URL}}/backend/get-quote/` will require authentication from Salesforce.
diff --git a/docs/edge-stack/latest/howtos/sso/uaa.md b/docs/edge-stack/latest/howtos/sso/uaa.md
new file mode 100644
index 000000000..4e0ebc9ba
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/sso/uaa.md
@@ -0,0 +1,72 @@
+# SSO with User Account and Authentication Service (UAA)
+
+**IMPORTANT:** $AESproductName$ requires the IdP to return a JWT signed by the RS256 algorithm (asymmetric key). Cloud Foundry's UAA defaults to symmetric key encryption which $AESproductName$ cannot read.
+
+1. When configuring UAA, you will need to provide your own asymmetric key in a file called `uaa.yml`. For example:
+
+ ```yaml
+ jwt:
+ token:
+ signing-key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA7Z1HBM6QFqnIJ1UA3NWnYMuubt4XlfbP1/GopTWUmchKataM
+ ...
+ ...
+ QSbJdIbUBwL8BcrfNw4ebp1DgTI9F45Re+evky0A82aL0/BvBHu8og==
+ -----END RSA PRIVATE KEY-----
+ ```
+
+2. Create an OIDC Client:
+
+ ```
+ uaac client add ambassador --name ambassador-client --scope openid --authorized_grant_types authorization_code,refresh_token --redirect_uri {AMBASSADOR_URL}/.ambassador/oauth2/redirection-endpoint --secret CLIENT_SECRET
+ ```
+
+ **Note:** Change the value of `{AMBASSADOR_URL}` with the IP or DNS of your $AESproductName$ load balancer.
+
+## Configure Filter and FilterPolicy
+
+Configure your OAuth `Filter` and `FilterPolicy` with the following:
+
+ Use the clientID (`ambassador`) and secret (`CLIENT_SECRET`) from Step 2 to configure the OAuth `Filter`.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Filter
+ metadata:
+ name: uaa-filter
+ namespace: default
+ spec:
+ OAuth2:
+ authorizationURL: {UAA_DOMAIN}
+ audience: {UAA_DOMAIN}
+ clientID: ambassador
+ secret: CLIENT_SECRET
+ protectedOrigins:
+ - origin: https://datawire-ambassador.com
+ ```
+
+ **Note:** The `authorizationURL` and `audience` are the same for UAA configuration.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: FilterPolicy
+ metadata:
+ name: httpbin-policy
+ namespace: default
+ spec:
+ rules:
+ - host: "*"
+ path: /httpbin/ip
+ filters:
+ - name: uaa-filter ## Enter the Filter name from above
+ arguments:
+ scope:
+ - "openid"
+ ```
+
+**Note:** The `scope` field was set when creating the client in
+Step 2. You can add any scope values you would like when creating the
+client.
diff --git a/docs/edge-stack/latest/howtos/token-ratelimit.md b/docs/edge-stack/latest/howtos/token-ratelimit.md
new file mode 100644
index 000000000..35912ac92
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/token-ratelimit.md
@@ -0,0 +1,154 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Rate limiting on token claims
+
+This guide applies to $AESproductName$, use of this guide on the $OSSproductName$ is not recommended.
+
+$AESproductName$ is able to perform Rate Limiting based on JWT Token claims from either a JWT or OAuth2 Filter implementation. This is because $AESproductName$ deliberately calls the `ext_authz` filter in Envoy as the first step when processing incoming requests. In $AESproductName$, the `ext_authz` filter is implemented as a [Filter resource](../../topics/using/filters/). This explicitly means that $AESproductName$ Filters are ALWAYS processed prior to RateLimit implementations. As a result, you can use the `injectRequestHeader` field in either a JWT Filter or an OAuth Filter and pass that header along to be used for RateLimiting purposes.
+
+## Prerequisites
+
+- $AESproductName$
+- A working Keycloak instance and Keycloak Filter
+- A service exposed with a Mapping and protected by a FilterPolicy
+
+We'll use Keycloak to generate tokens with unique claims. It will work in a similar manner for any claims present on a JWT token issued by any other provider. See our guide here on using Keycloak with $AESproductName$.
+
+Here is a YAML example that describes the setup:
+
+```yaml
+---
+# Mapping to expose the Quote service
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: quote-backend
+spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+---
+# Basic OAuth filter for Keycloak
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: keycloak-filter-ambassador
+spec:
+ OAuth2:
+ authorizationURL: https:///auth/realms/
+ audience:
+ clientID:
+ secret:
+ protectedOrigins:
+ - origin: https://host.example.com
+---
+# Basic FilterPolicy that covers everything
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: ambassador-policy
+spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: keycloak-filter-ambassador
+```
+
+## 1. Configure the Filter to extract the claim
+
+In order to extract the claim, we need to have the Filter use the `injectRequestHeader` config and use a golang template to pull out the exact value of the `name` claim in our access token JWT and put it in a Header for our RateLimit to catch. Configuration is similar for both [OAuth2](../../topics/using/filters/oauth2/) and [JWT](../../topics/using/filters/jwt/).
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: keycloak-filter-ambassador
+spec:
+ OAuth2:
+ authorizationURL: https:///auth/realms/
+ audience:
+ clientID:
+ secret:
+ protectedOrigins:
+ - origin: https://host.example.com
+ injectRequestHeaders:
+ - name: "x-token-name"
+ value: "{{ .token.Claims.name }}" # This extracts the "name" claim and puts it in the "x-token-name" header.
+```
+
+## 2. Add Labels to our Mapping
+
+Now that the header is properly added, we need to add a label to the Mapping of the service that we want to rate limit. This will determine if the route established by the Mapping will use a label when $AESproductName$ is processing where to send the request. If so, it will add the labels as metadata to be attached when sent to the `RateLimitService` to determine whether or not the request should be rate-limited.
+
+Use `ambassador` as the label domain, unless you have already set up $AESproductName$ to use something else.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: quote-backend
+spec:
+ hostname: "*"
+ prefix: /backend/
+ service: quote
+ labels:
+ ambassador:
+ - header_request_label:
+ - request_headers:
+ key: headerkey # In pattern matching, they key queried will be "headerkey" and the value
+ header_name: "x-token-name" # queried will be the value of "x-token-name" header
+```
+
+## 3. Create our RateLimit
+
+We now have appropriate labels added to the request when we send it to the rate limit service, but how do we know what rate limit to apply and how many requests should we allow before returning an error? This is where the RateLimit comes in. The RateLimit allows us to create specific rules based on the labels associated with a particular request. If a value is not specified, then each unique value of the `x-token-name` header that comes in will be associated with its own counter. So, someone with a `name` JWT claim of "Julian" will be tracked separately from "Jane".
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: token-name-rate-limit
+spec:
+ domain: ambassador
+ - name: token-name-per-minute
+ action: Enforce
+ pattern:
+ - headerkey: "" # Each unique header value of "x-token-name" will be tracked individually
+ rate: 10
+ unit: "minute" # Per-minute tracking is useful for debugging
+```
+
+## 4. Test
+
+Now we can navigate to our backend in a browser at `https://host.example.com/backend/`. After logging in, if we keep refreshing, we will find that our 11th attempt will respond with a blank page. Success!
+
+## 5. Enforce a different rate limit for a specific user
+
+We've noticed that the user "Julian" uses bad code that abuses the API and consumes way too much bandwidth with his retries. As such, we want a user with the exact `name` claim of "Julian" to only get 2 requests per minute before getting an error.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: token-name-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - name: julians-rule-enforcement
+ action: Enforce
+ pattern:
+ - headerkey: "Julian" # Only matches for x-token-name = "Julian"
+ rate: 2
+ unit: "minute"
+ - name: token-name-per-minute
+ action: Enforce
+ pattern:
+ - headerkey: "" # Each unique header value of "x-token-name" will be tracked individually
+ rate: 10
+ unit: "minute" # Per-minute tracking is useful for debugging
+```
+
+This tutorial only scratches the surface of the rate limiting capabilities of $AESproductName$. Please see our documentation [here](../../topics/using/rate-limits/) and [here](../../topics/using/rate-limits/rate-limits/) to learn more about how you can use rate limiting.
diff --git a/docs/edge-stack/latest/howtos/web-application-firewalls-config.md b/docs/edge-stack/latest/howtos/web-application-firewalls-config.md
new file mode 100644
index 000000000..323e3da78
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/web-application-firewalls-config.md
@@ -0,0 +1,59 @@
+---
+ Title: Configuring Web Application Firewall rules in Edge Stack
+ description: Get Web Application Firewalls quickly setup with Edge Stack and create custom firewall rules.
+---
+
+# Configuring Web Application Firewall rules in $productName$
+
+When writing your own firewall rules it's important to first take note of a few ways that $productName$'s `WebApplicationFirewalls` work.
+
+1. Requests are either denied or allowed, redirects and dropped requests are not supported
+2. If you have a rule in your firewall configuration that specifies the `deny` action and you do not specify a `status`, then we will default to
+using status code `403`.
+3. State is not preserved across the different phases of proceeing a request. For this reason it is advised to use early blocking mode
+rather than anamoly scoring mode and to avoid creating any firewall rules that require state or information created by rules in a different phase. For more information about waf phases refer to the [Coraza Seclang Execution Flow docs][].
+
+## Ambassador Labs Firewall Ruleset
+
+Ambassador Labs publishes and maintains a set of firewall rules that are ready to use.
+The latest version of the Ambassador Labs Web Application Firewall ruleset can be downloaded with these commands:
+
+```bash
+wget https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf
+wget https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf
+wget https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf
+```
+
+Each file must be imported into $productName$'s Web Application Firewall in the following order:
+
+1. aes-waf.conf
+2. crs-setup.conf
+3. waf-rules.conf
+
+The Ambassador Labs ruleset largely focuses on incoming requests and by default it does not perform processing on response bodies from upstream services to minimize the request round-trip latency.
+
+If processing of responses is desired, then you can create your own custom rule set or add additional rules to be loaded after the Ambassador Labs ruleset to add custom validation of responses from upstream services.
+
+If you are adding rules to process response bodies after the Ambassador Labs ruleset, then you will need to set `SecResponseBodyAccess On` in your rules to enable access to the response body.
+
+If you'd like to customize the Ambassador Labs default ruleset, you can load your own files before or after waf-rules.conf. Keep in mind that the `WebApplicationFirewall` resource loads firewall configurations via a list of rules sources, and sources lower in the list can overwrite rules and settings from sources higher in the list. See files [REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example][] and [RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example][] for more information.
+
+## Web Application Firewall Rules Release Notes
+
+
+To install any of the rules below, import all the files for the desired version in the order they are listed.
+
+
+### Version v1-20230825
+
+Initial version of $productName$'s Web Application Firewall rules.
+
+Files:
+
+- [aes-waf.conf](https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf)
+- [crs-setup.conf](https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf)
+- [waf-rules.conf](https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf)
+
+[REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example
+[RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
+[Coraza Seclang Execution Flow docs]: https://coraza.io/docs/seclang/execution-flow/
diff --git a/docs/edge-stack/latest/howtos/web-application-firewalls-in-production.md b/docs/edge-stack/latest/howtos/web-application-firewalls-in-production.md
new file mode 100644
index 000000000..4b8d61db3
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/web-application-firewalls-in-production.md
@@ -0,0 +1,223 @@
+---
+ Title: Using Web Application Firewalls in production
+ description: Learn about best practices for enabling Edge Stack's Web Application Firewalls in production environments
+---
+
+# Using Web Application Firewall in production
+
+By default, Ambassador Labs rules are configured to block malicious requests. However, when a Web Application Firewall is
+first deployed in a production environment, it is recommended to set it in a non-blocking mode and monitor its behavior
+to identify potential issues.
+
+The following procedure can be followed to deploy $productName$'s Web Application Firewall in detection-only mode and
+customize the rules.
+
+1. Enable Detection Only mode. Detection Only mode will run all rules, but won't execute any disruptive actions.
+ This is configured using the directive [SecRuleEngine][].
+
+ You also want to enable debug logs, which are necessary to identify false positives. You can them in the
+ `WebApplicationFirewall` resource as described in the [documentation][].
+
+ Optionally, Coraza debug logs can be enabled by setting the directive [SecDebugLogLevel][]. These logs are very verbose
+ but can help identify issues when the `WebApplicationFirewall` logs don't show enough information.
+
+ The following example illustrates this:
+
+ ```yaml
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: "waf-configuration"
+ data:
+ waf-overrides.conf: |
+ SecRuleEngine DetectionOnly
+ SecDebugLogLevel 4
+
+ ---
+
+ apiVersion: gateway.getambassador.io/v1alpha1
+ kind: WebApplicationFirewall
+ metadata:
+ name: "waf-rules"
+ spec:
+ firewallRules:
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf"
+ - configMapRef:
+ key: waf-overrides.conf
+ name: waf-configuration
+ sourceType: configmap
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf"
+ logging:
+ onInterrupt:
+ enabled: true
+ ```
+
+2. Identify false positives. $productName$'s container logs will have one or more entries indicating which rules
+ were applied to a request and why.
+
+ For example, the following log entry (formatted for readability) shows that a request to `https://34.123.92.3/backend/` was
+ blocked by rule 920350 because the Host header contains an IP address.
+
+ ```text
+ 2023-06-14T17:37:29.145Z INFO waf/manager.go:73 request interrupted by waf: default/example-waf
+ {
+ "message": "Host header is a numeric IP address",
+ "data": "34.123.92.3",
+ "uri": "https://34.123.92.3/backend/",
+ "disruptive": true,
+ "matchedDatas": [
+ {
+ "Variable_": 54,
+ "Key_": "Host",
+ "Value_": "34.123.92.3",
+ "Message_": "Host header is a numeric IP address",
+ "Data_": "34.123.92.3",
+ "ChainLevel_": 0
+ }
+ ],
+ "rule": {
+ "ID_": 920350,
+ "File_": "",
+ "Line_": 9892,
+ "Rev_": "",
+ "Severity_": 4,
+ "Version_": "OWASP_CRS/4.0.0-rc1",
+ "Tags_": [
+ "application-multi",
+ "language-multi",
+ "platform-multi",
+ "attack-protocol",
+ "paranoia-level/1",
+ "OWASP_CRS",
+ "capec/1000/210/272",
+ "PCI/6.5.10"
+ ],
+ "Maturity_": 0,
+ "Accuracy_": 0,
+ "Operator_": "",
+ "Phase_": 1,
+ "Raw_": "SecRule REQUEST_HEADERS:Host \"@rx (?:^([\\d.]+|\\[[\\da-f:]+\\]|[\\da-f:]+)(:[\\d]+)?$)\" \"id:920350,phase:1,block,t:none,msg:'Host header is a numeric IP address',logdata:'%{MATCHED_VAR}',tag:'application-multi',tag:'language-multi',tag:'platform-multi',tag:'attack-protocol',tag:'paranoia-level/1',tag:'OWASP_CRS',tag:'capec/1000/210/272',tag:'PCI/6.5.10',ver:'OWASP_CRS/4.0.0-rc1',severity:'WARNING',setvar:'tx.inbound_anomaly_score_pl1=+%{tx.warning_anomaly_score}'\"",
+ "SecMark_": ""
+ }
+ }
+ ```
+
+ If you enabled Coraza debug logs, use the rule ID to identify entries that are not important as follows:
+
+ - Rules in the range 900000 to 901999 define some Coraza behaviors and can be ignored.
+
+ - Rules like the one below are used to skip other rules and can be ignored as well.
+
+ ```text
+ SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:911012,phase:2,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
+ ```
+
+
+ Each Web Application Firewall configuration file has rules in predefined ranges as follows: Rules in the range
+ 900000 to 900999 are in crs-setup.conf, rules IDs 901000 to 999999 are in waf-rules.conf, and all other rules are in aes-waf.conf.
+
+
+
+## Customizing Ambassador Labs rules
+
+There are several options to configure if/when a rule runs:
+1. Disable a rule completely.
+2. Apply a rule to some requests.
+
+### Disabling a rule completely
+
+To disable a rule, follow the instructions in [RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example][], save the
+configuration as a ConfigMap, and load it after `waf-rules.conf`.
+
+For example, let's say that we want to disable the rule with ID `913110`. The first step is to create the configuration:
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: "waf-configuration"
+data:
+ disabled-rules.conf: |
+ SecRuleRemoveById 913110
+```
+
+And then load it after `waf-rules.conf`:
+
+```yaml
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewall
+metadata:
+ name: "waf-rules"
+spec:
+ firewallRules:
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf"
+ - configMapRef:
+ key: disabled-rules.conf
+ name: waf-configuration
+ sourceType: configmap
+```
+
+### Applying a rule to some requests
+
+To apply a rule only to some requests, update it as described in [REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example][] and
+load the new settings before `waf-rules.conf`.
+
+The following example shows how to disable all rules tagged `attack-sqli` when the URI does not start with '/api/':
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: "waf-configuration"
+data:
+ website-rules.conf: |
+ SecRule REQUEST_URI "!@beginsWith /api/" \
+ "id:1000,\
+ phase:2,\
+ pass,\
+ nolog,\
+ ctl:ruleRemoveByTag=attack-sqli"
+
+---
+
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewall
+metadata:
+ name: "waf-rules"
+spec:
+ firewallRules:
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/aes-waf.conf"
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/crs-setup.conf"
+ - configMapRef:
+ key: website-rules.conf
+ name: waf-configuration
+ sourceType: configmap
+ - sourceType: "http"
+ http:
+ url: "https://app.getambassador.io/download/waf/v1-20230825/waf-rules.conf"
+```
+
+[SecRuleEngine]: https://coraza.io/docs/seclang/directives/#secruleengine
+[SecDebugLogLevel]: https://coraza.io/docs/seclang/directives/#secdebugloglevel
+[REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example
+[RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example]: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
+[documentation]: ../web-application-firewalls
diff --git a/docs/edge-stack/latest/howtos/web-application-firewalls.md b/docs/edge-stack/latest/howtos/web-application-firewalls.md
new file mode 100644
index 000000000..d2c6eb06f
--- /dev/null
+++ b/docs/edge-stack/latest/howtos/web-application-firewalls.md
@@ -0,0 +1,142 @@
+---
+ Title: Protect your services with Edge Stack's Web Application Firewalls
+ description: Quickly block common attacks in the OWASP Top 10 vulnerabilities like cross-site-scripting (XSS) and SQL injection with Edge Stack's self-service Web Application Firewalls (WAF)
+---
+
+# Using Web Application Firewalls in $productName$
+
+[$productName$][] comes fully equipped with a Web Application Firewall solution (commonly referred to as WAF) that is easy to set up and can be configured to help protect your web applications by preventing and mitigating many common attacks. To accomplish this, the [Coraza Web Application Firewall library][] is used to check incoming requests against a user-defined configuration file containing rules and settings for the firewall to determine whether to allow or deny incoming requests.
+
+
+
+$productName$ also has additional authentication features such as [Filters][] and [Rate Limiting][]. When `Filters`, `Ratelimits`, and `WebApplicationFirewalls` are all used at the same time, the order of operations is as follows and is not currently configurable.
+
+1. `WebApplicationFirewalls` are always executed first
+2. `Filters` are executed next (so long as any configured `WebApplicationFirewalls` did not already reject the request)
+3. Lastly `Ratelimits` are executed (so long as any configured `WebApplicationFirewalls` and Filters did not already reject the request)
+
+## Quickstart
+
+See the [WebAplicationFirewall API reference][] and [WebAplicationFirewallPolicy API reference][]
+pages for an overview of all the supported fields of the following custom resources.
+
+1. First, start by creating your firewall configuration. The example will download [the firewall rules][] published by [Ambassador Labs][], but you are free to write your own or use the published rules as a reference.
+
+ ```yaml
+ kubectl apply -f -</test -H 'User-Agent: Arachni/0.2.1'
+ ```
+
+Congratulations, you've successfully set up a Web Application Firewall to secure all requests coming into $productName$.
+
+
+ After applying your WebApplicationFirewall and WebApplicationFirewall resources, check their statuses to make sure that they were not rejected due to any configuration errors.
+
+
+## Rules for Web Application Firewalls
+
+Since the [Coraza Web Application Firewall library][] $productName$'s Web Application Firewall implementation, the firewall rules configuration uses [Coraza's Seclang syntax][] which is compatible with the OWASP Core Rule Set.
+
+Ambassador Labs publishes and maintains a list of rules to be used with the Web Application Firewall that should be a good solution for most users and [Coraza also provides their own ruleset][] based on the [OWASP][] core rule set. It also
+satisifies [PCI 6.6][] compliance requirements.
+
+Ambassador Labs rules differ from the OWASP Core ruleset in the following areas:
+
+- WAF engine is enabled by default.
+- A more comprehensive set of rules is enabled, including rules related to compliance with PCI DSS 6.5 and 12.1 requirements.
+
+See [Configuring $productName$'s Web Application Firewall rules][] for more information about installing Ambassador Labs rules.
+
+For specific information about rule configuration, please refer to [Coraza's Seclang documentation][]
+
+## Observability
+
+To make using $productName$'s Web Application Firewall system easier and to enable automated workflows and alerts, there are three main methods of observability for Web Application Firewall behavior.
+
+### Logging
+
+ $productName$ will log information about requests approved and denied by any `WebApplicationFirewalls` along with the reason why the request was denied.
+ You can configure the logging policies in the [coraza rules configuration][] where logs are sent to and how much information is logged.
+ Ambassador Labs' default ruleset sends the WAF logs to stdout so they show up in the container logs.
+
+### Metrics
+
+ $productName$ also outputs metrics about the Web Application Firewall, including the number of requests approved and denied, and performance information.
+
+| Metric | Type | Description | |
+|-------------------------------------|-----------------------|-----------------------------------------------------------------------------------------------|
+| `waf_created_wafs` | Gauge | Number of created web application firewall |
+| `waf_managed_wafs_total` | Counter | Number of managed web application firewalls |
+| `waf_added_latency_ms` | Histogram | Added latency in milliseconds |
+| `waf_total_denied_requests_total` | Counter (with labels) | Number of requests denied by any web application firewall |
+| `waf_total_denied_responses_total` | Counter (with labels) | Number of responses denied by any web application firewall |
+| `waf_denied_breakdown_total` | Counter (with labels) | Breakdown of requests/responses denied and the web application firewall that denied them |
+| `waf_total_allowed_requests_total` | Counter (with labels) | Number of requests allowed by any web application firewall |
+| `waf_total_allowed_responses_total` | Counter (with labels) | Number of responses allowed by any web application firewall |
+| `waf_allowed_breakdown_total` | Counter (with labels) | Breakdown of requests/responses allowed and the web application firewall that allowed them |
+| `waf_errors` | Counter (with labels) | Tracker for any errors encountered by web application firewalls and the reason for the error |
+
+### Grafana Dashboard
+
+ $productName$ provides a [Grafana dashboard][] that can be imported to [Grafana][]. In addition, the dashboard has pre-built panels that help visualize the metrics that are collected about Web Application Firewall activity. For more information about getting [Prometheus][] and Grafana set up for gathering and visualizing metrics from $productName$ please refer to the [Prometheus and Grafana documentation][].
+
+[Coraza Web Application Firewall library]: https://coraza.io/docs/tutorials/introduction/
+[Filters]: ../../topics/using/filters
+[Rate limiting]: ../../topics/using/rate-limits/rate-limits#rate-limiting-reference
+[Coraza's Seclang syntax]: https://coraza.io/docs/seclang/directives/
+[Coraza also provides their own ruleset]: https://coraza.io/docs/tutorials/coreruleset/
+[Coraza's Seclang documentation]: https://coraza.io/docs/seclang/
+[OWASP]: https://owasp.org/
+[PCI 6.6]: https://listings.pcisecuritystandards.org/documents/information_supplement_6.6.pdf
+[Grafana dashboard]: https://grafana.com/grafana/dashboards/4698-ambassador-edge-stack/
+[Grafana]: https://grafana.com/
+[Prometheus]: https://prometheus.io/docs/introduction/overview/
+[Prometheus and Grafana documentation]:../prometheus
+[WebAplicationFirewall API reference]: ../../custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall
+[WebAplicationFirewallPolicy API reference]: ../../custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy
+[$productName$]: https://www.getambassador.io/products/edge-stack/api-gateway
+[Ambassador Labs]: https://www.getambassador.io/
+[Configuring $productName$'s Web Application Firewall rules]: ../web-application-firewalls-config
+[coraza rules configuration]: https://coraza.io/docs/seclang/directives/#secauditlog
+[the firewall rules]: ../web-application-firewalls-config
diff --git a/docs/edge-stack/latest/images/Auth0_audience.png b/docs/edge-stack/latest/images/Auth0_audience.png
new file mode 100644
index 000000000..6cb706817
Binary files /dev/null and b/docs/edge-stack/latest/images/Auth0_audience.png differ
diff --git a/docs/edge-stack/latest/images/Auth0_none.png b/docs/edge-stack/latest/images/Auth0_none.png
new file mode 100644
index 000000000..1e87f6c0e
Binary files /dev/null and b/docs/edge-stack/latest/images/Auth0_none.png differ
diff --git a/docs/edge-stack/latest/images/Auth0_secret.png b/docs/edge-stack/latest/images/Auth0_secret.png
new file mode 100644
index 000000000..d0636a50d
Binary files /dev/null and b/docs/edge-stack/latest/images/Auth0_secret.png differ
diff --git a/docs/edge-stack/latest/images/ambassador_oidc_flow.jpg b/docs/edge-stack/latest/images/ambassador_oidc_flow.jpg
new file mode 100644
index 000000000..4f1c0c7e6
Binary files /dev/null and b/docs/edge-stack/latest/images/ambassador_oidc_flow.jpg differ
diff --git a/docs/edge-stack/latest/images/create-application.png b/docs/edge-stack/latest/images/create-application.png
new file mode 100644
index 000000000..d181be2ed
Binary files /dev/null and b/docs/edge-stack/latest/images/create-application.png differ
diff --git a/docs/edge-stack/latest/images/docker.png b/docs/edge-stack/latest/images/docker.png
new file mode 100644
index 000000000..1f35e5ea4
Binary files /dev/null and b/docs/edge-stack/latest/images/docker.png differ
diff --git a/docs/edge-stack/latest/images/edge-stack-1.13.10-consul-cert-log.png b/docs/edge-stack/latest/images/edge-stack-1.13.10-consul-cert-log.png
new file mode 100644
index 000000000..1e045bf42
Binary files /dev/null and b/docs/edge-stack/latest/images/edge-stack-1.13.10-consul-cert-log.png differ
diff --git a/docs/edge-stack/latest/images/edge-stack-1.13.10-docs-timeout.png b/docs/edge-stack/latest/images/edge-stack-1.13.10-docs-timeout.png
new file mode 100644
index 000000000..1dc9087be
Binary files /dev/null and b/docs/edge-stack/latest/images/edge-stack-1.13.10-docs-timeout.png differ
diff --git a/docs/edge-stack/latest/images/edge-stack-1.13.4.png b/docs/edge-stack/latest/images/edge-stack-1.13.4.png
new file mode 100644
index 000000000..954ac1a9c
Binary files /dev/null and b/docs/edge-stack/latest/images/edge-stack-1.13.4.png differ
diff --git a/docs/edge-stack/latest/images/edge-stack-1.13.7-json-logging.png b/docs/edge-stack/latest/images/edge-stack-1.13.7-json-logging.png
new file mode 100644
index 000000000..4a47cbdfc
Binary files /dev/null and b/docs/edge-stack/latest/images/edge-stack-1.13.7-json-logging.png differ
diff --git a/docs/edge-stack/latest/images/edge-stack-1.13.7-memory.png b/docs/edge-stack/latest/images/edge-stack-1.13.7-memory.png
new file mode 100644
index 000000000..9c415ba36
Binary files /dev/null and b/docs/edge-stack/latest/images/edge-stack-1.13.7-memory.png differ
diff --git a/docs/edge-stack/latest/images/edge-stack-1.13.7-tcpmapping-consul.png b/docs/edge-stack/latest/images/edge-stack-1.13.7-tcpmapping-consul.png
new file mode 100644
index 000000000..c455a47f1
Binary files /dev/null and b/docs/edge-stack/latest/images/edge-stack-1.13.7-tcpmapping-consul.png differ
diff --git a/docs/edge-stack/latest/images/edge-stack-1.13.8-cloud-bugfix.png b/docs/edge-stack/latest/images/edge-stack-1.13.8-cloud-bugfix.png
new file mode 100644
index 000000000..6beaf653b
Binary files /dev/null and b/docs/edge-stack/latest/images/edge-stack-1.13.8-cloud-bugfix.png differ
diff --git a/docs/edge-stack/latest/images/emissary-1.13.10-cors-origin.png b/docs/edge-stack/latest/images/emissary-1.13.10-cors-origin.png
new file mode 100644
index 000000000..b7538e5f4
Binary files /dev/null and b/docs/edge-stack/latest/images/emissary-1.13.10-cors-origin.png differ
diff --git a/docs/edge-stack/latest/images/helm-navy.png b/docs/edge-stack/latest/images/helm-navy.png
new file mode 100644
index 000000000..a97101435
Binary files /dev/null and b/docs/edge-stack/latest/images/helm-navy.png differ
diff --git a/docs/edge-stack/latest/images/jaeger.png b/docs/edge-stack/latest/images/jaeger.png
new file mode 100644
index 000000000..3b821c09e
Binary files /dev/null and b/docs/edge-stack/latest/images/jaeger.png differ
diff --git a/docs/edge-stack/latest/images/kubernetes.png b/docs/edge-stack/latest/images/kubernetes.png
new file mode 100644
index 000000000..a392a886b
Binary files /dev/null and b/docs/edge-stack/latest/images/kubernetes.png differ
diff --git a/docs/edge-stack/latest/images/machine-machine.png b/docs/edge-stack/latest/images/machine-machine.png
new file mode 100644
index 000000000..32a112f9c
Binary files /dev/null and b/docs/edge-stack/latest/images/machine-machine.png differ
diff --git a/docs/edge-stack/latest/images/mapping-editor.png b/docs/edge-stack/latest/images/mapping-editor.png
new file mode 100644
index 000000000..f8b751a19
Binary files /dev/null and b/docs/edge-stack/latest/images/mapping-editor.png differ
diff --git a/docs/edge-stack/latest/images/scopes.png b/docs/edge-stack/latest/images/scopes.png
new file mode 100644
index 000000000..f78d22a0c
Binary files /dev/null and b/docs/edge-stack/latest/images/scopes.png differ
diff --git a/docs/edge-stack/latest/images/xkcd.png b/docs/edge-stack/latest/images/xkcd.png
new file mode 100644
index 000000000..ed0d5c33b
Binary files /dev/null and b/docs/edge-stack/latest/images/xkcd.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-1.13.4.png b/docs/edge-stack/latest/release-notes/edge-stack-1.13.4.png
new file mode 100644
index 000000000..954ac1a9c
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-1.13.4.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-json-logging.png b/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-json-logging.png
new file mode 100644
index 000000000..4a47cbdfc
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-json-logging.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-memory.png b/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-memory.png
new file mode 100644
index 000000000..9c415ba36
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-memory.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-tcpmapping-consul.png b/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-tcpmapping-consul.png
new file mode 100644
index 000000000..c455a47f1
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-1.13.7-tcpmapping-consul.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-1.13.8-cloud-bugfix.png b/docs/edge-stack/latest/release-notes/edge-stack-1.13.8-cloud-bugfix.png
new file mode 100644
index 000000000..6beaf653b
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-1.13.8-cloud-bugfix.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-host_crd.png b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-host_crd.png
new file mode 100644
index 000000000..c77ef5287
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-host_crd.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-ingressstatus.png b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-ingressstatus.png
new file mode 100644
index 000000000..6856d308d
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-ingressstatus.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-insecure_action_hosts.png b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-insecure_action_hosts.png
new file mode 100644
index 000000000..79c20bad1
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-insecure_action_hosts.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-listener.png b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-listener.png
new file mode 100644
index 000000000..ea45a02ba
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-listener.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-prune_routes.png b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-prune_routes.png
new file mode 100644
index 000000000..bc43229fc
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-prune_routes.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-tlscontext.png b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-tlscontext.png
new file mode 100644
index 000000000..68dbad807
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-tlscontext.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-v3alpha1.png b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-v3alpha1.png
new file mode 100644
index 000000000..c0ac35962
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-2.0.0-v3alpha1.png differ
diff --git a/docs/edge-stack/latest/release-notes/edge-stack-GA.png b/docs/edge-stack/latest/release-notes/edge-stack-GA.png
new file mode 100644
index 000000000..2e6341881
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/edge-stack-GA.png differ
diff --git a/docs/edge-stack/latest/release-notes/emissary-1.13.10-cors-origin.png b/docs/edge-stack/latest/release-notes/emissary-1.13.10-cors-origin.png
new file mode 100644
index 000000000..b7538e5f4
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/emissary-1.13.10-cors-origin.png differ
diff --git a/docs/edge-stack/latest/release-notes/tada.png b/docs/edge-stack/latest/release-notes/tada.png
new file mode 100644
index 000000000..c8832e8e3
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/tada.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.4-k8s-1.22.png b/docs/edge-stack/latest/release-notes/v2.0.4-k8s-1.22.png
new file mode 100644
index 000000000..ed9b04158
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.4-k8s-1.22.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.4-l7depth.png b/docs/edge-stack/latest/release-notes/v2.0.4-l7depth.png
new file mode 100644
index 000000000..9314324cb
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.4-l7depth.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.4-mapping-dns-type.png b/docs/edge-stack/latest/release-notes/v2.0.4-mapping-dns-type.png
new file mode 100644
index 000000000..7770c77d2
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.4-mapping-dns-type.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.4-v3alpha1.png b/docs/edge-stack/latest/release-notes/v2.0.4-v3alpha1.png
new file mode 100644
index 000000000..9c50b8fb8
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.4-v3alpha1.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.4-version.png b/docs/edge-stack/latest/release-notes/v2.0.4-version.png
new file mode 100644
index 000000000..9481b7dbd
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.4-version.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.5-auth-circuit-breaker.png b/docs/edge-stack/latest/release-notes/v2.0.5-auth-circuit-breaker.png
new file mode 100644
index 000000000..cac8cf7b2
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.5-auth-circuit-breaker.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.5-cache-change.png b/docs/edge-stack/latest/release-notes/v2.0.5-cache-change.png
new file mode 100644
index 000000000..8471ab3fa
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.5-cache-change.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.0.5-mappingselector.png b/docs/edge-stack/latest/release-notes/v2.0.5-mappingselector.png
new file mode 100644
index 000000000..31942ede6
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.0.5-mappingselector.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.0-canary.png b/docs/edge-stack/latest/release-notes/v2.1.0-canary.png
new file mode 100644
index 000000000..39d3bbbfb
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.0-canary.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.0-edge-stack-validation.png b/docs/edge-stack/latest/release-notes/v2.1.0-edge-stack-validation.png
new file mode 100644
index 000000000..dc82e2821
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.0-edge-stack-validation.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.0-gzip-enabled.png b/docs/edge-stack/latest/release-notes/v2.1.0-gzip-enabled.png
new file mode 100644
index 000000000..061fcbc97
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.0-gzip-enabled.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.0-smoother-migration.png b/docs/edge-stack/latest/release-notes/v2.1.0-smoother-migration.png
new file mode 100644
index 000000000..ebd77497d
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.0-smoother-migration.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.2-annotations.png b/docs/edge-stack/latest/release-notes/v2.1.2-annotations.png
new file mode 100644
index 000000000..b5498c3c1
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.2-annotations.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.2-filter-jwtassertion.png b/docs/edge-stack/latest/release-notes/v2.1.2-filter-jwtassertion.png
new file mode 100644
index 000000000..da58bdd91
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.2-filter-jwtassertion.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.2-host-mapping-matching.png b/docs/edge-stack/latest/release-notes/v2.1.2-host-mapping-matching.png
new file mode 100644
index 000000000..1cfba5ede
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.2-host-mapping-matching.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.2-mapping-cors.png b/docs/edge-stack/latest/release-notes/v2.1.2-mapping-cors.png
new file mode 100644
index 000000000..f76ea01ca
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.2-mapping-cors.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.2-mapping-less-weighted.png b/docs/edge-stack/latest/release-notes/v2.1.2-mapping-less-weighted.png
new file mode 100644
index 000000000..7e299062e
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.2-mapping-less-weighted.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.1.2-mapping-no-rewrite.png b/docs/edge-stack/latest/release-notes/v2.1.2-mapping-no-rewrite.png
new file mode 100644
index 000000000..5d3d5a29f
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.1.2-mapping-no-rewrite.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.2.0-cloud.png b/docs/edge-stack/latest/release-notes/v2.2.0-cloud.png
new file mode 100644
index 000000000..5923fcb44
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.2.0-cloud.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.2.0-percent-escape.png b/docs/edge-stack/latest/release-notes/v2.2.0-percent-escape.png
new file mode 100644
index 000000000..df4d81b94
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.2.0-percent-escape.png differ
diff --git a/docs/edge-stack/latest/release-notes/v2.2.0-tls-cert-validation.png b/docs/edge-stack/latest/release-notes/v2.2.0-tls-cert-validation.png
new file mode 100644
index 000000000..f8635b5af
Binary files /dev/null and b/docs/edge-stack/latest/release-notes/v2.2.0-tls-cert-validation.png differ
diff --git a/docs/edge-stack/latest/releaseNotes.yml b/docs/edge-stack/latest/releaseNotes.yml
new file mode 100644
index 000000000..5fc4a845b
--- /dev/null
+++ b/docs/edge-stack/latest/releaseNotes.yml
@@ -0,0 +1,2021 @@
+# -*- fill-column: 100 -*-
+
+# This file should be placed in the folder for the version of the
+# product that's meant to be documented. A `/release-notes` page will
+# be automatically generated and populated at build time.
+#
+# Note that an entry needs to be added to the `doc-links.yml` file in
+# order to surface the release notes in the table of contents.
+#
+# The YAML in this file should contain:
+#
+# changelog: An (optional) URL to the CHANGELOG for the product.
+# items: An array of releases with the following attributes:
+# - version: The (optional) version number of the release, if applicable.
+# - date: The date of the release in the format YYYY-MM-DD.
+# - notes: An array of noteworthy changes included in the release, each having the following attributes:
+# - type: The type of change, one of `bugfix`, `feature`, `security` or `change`.
+# - title: A short title of the noteworthy change.
+# - body: >-
+# Two or three sentences describing the change and why it
+# is noteworthy. This is HTML, not plain text or
+# markdown. It is handy to use YAML's ">-" feature to
+# allow line-wrapping.
+# - image: >-
+# The URL of an image that visually represents the
+# noteworthy change. This path is relative to the
+# `release-notes` directory; if this file is
+# `FOO/releaseNotes.yml`, then the image paths are
+# relative to `FOO/release-notes/`.
+# - docs: The path to the documentation page where additional information can be found.
+# - href: A path from the root to a resource on the getambassador website, takes precedence over a docs link.
+
+changelog: https://github.com/datawire/edge-stack/blob/$branch$/CHANGELOG.md
+items:
+ - version: 3.9.0
+ date: '2023-11-13'
+ notes:
+ - title: gateway.getambassador.io/v1alpha1 Filter & FilterPolicy resources
+ type: feature
+ body: >-
+ Filter and FilterPolicy resources are now available via gateway.getambassador.io/v1alpha1. It is NOT backwards compatible with getambassador.io/v3alpha1 Filter and FilterPolicy resources. These are the next generation of CRD's that you can
+ progressively adopt over time. They provide stronger typings so that
+ feedback is given at apply time rather than runtime.
+ docs: custom-resources/gateway-getambassador/v1alpha1/filter
+
+ - title: getambassador.io/v3alpha1 Filter & FilterPolicy statuses
+ type: feature
+ body: >-
+ Filter and FilterPolicy resources for the getambassador.io/v3alpha1 version will now provide statuses when they are "Ready". If there are
+ configuration errors they will provide an error message with details about the configuration issues. This will help troubleshoot configuration issues.
+
+ docs: custom-resources/getambassador/v3alpha1/filter
+
+ - title: Upgrade to Envoy 1.27.2
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.27.2 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.27.2 Release Notes
+ docs: https://www.envoyproxy.io/docs/envoy/v1.27.2/version_history/version_history
+
+ - title: Added support for RESOURCE_EXHAUSTED responses to grpc clients when rate limited
+ type: feature
+ body: >-
+ By default, $productName$ will return an UNAVAILABLE code when a request using gRPC is rate limited. The RateLimitService resource now exposes a new grpc.use_resource_exhausted_code field that when set to true, $productName$ will return a RESOURCE_EXHAUSTED gRPC code instead. Thanks to Jerome Froelich for contributing this feature!
+ docs: topics/using/running/aes-extensions/ratelimit
+
+ - title: Added support for setting specific Envoy runtime flags in the Module
+ type: feature
+ body: >-
+ Envoy runtime fields that were provided to mitigate the recent HTTP/2 rapid reset
+ vulnerability can now be configured via the Module resource so the configuration will
+ persist between restarts. This configuration is added to the Envoy bootstrap config, so
+ restarting Emissary is necessary after changing these fields for the configuration to take effect.
+ docs: topics/running/ambassador/#set-envoy-runtime-flags
+
+ - title: Update APIExt minimum TLS version
+ type: change
+ body: >-
+ APIExt would previously allow for TLS 1.0 connections. We have updated it to now only use a minimum TLS version of 1.3 to resolve security concerns.
+ docs: https://www.tenable.com/plugins/nessus/104743
+
+ - title: Shipped Helm chart v8.9.0
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.9.0.
+ docs: https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$
+
+ - title: Ensure APIExt server is available before starting Edge Stack
+ type: bugfix
+ body: >-
+ The APIExt server provides CRD conversion between the stored version
+ v2 and the version watched for by $productName$ v3alpha1. Since this
+ component is required to operate $productName$, we have introduced
+ an init container that will ensure it is available before starting. This will help address some of the intermittent issues seen during
+ install and upgrades.
+ docs: https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$
+
+ - version: 3.8.2
+ date: '2023-10-11'
+ notes:
+ - title: Upgrade Envoy
+ type: security
+ body: >-
+ This release includes security patches to the current Envoy proxy version to address CVE 2023-44487 and includes a fix to determine if a client is making too many requests with premature resets. The connection is disconnected if more than 50 percent of resets are considered premature. Another fix is also included which exposes a runtime setting to control the limit on the number of HTTP requests processed from a single connection in a single I/O cycle to mitigate CPU starvation.
+ docs: topics/running/running/
+
+ - title: Upgrade Golang to 1.20.10
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This update resolves CVE-2023-39323 and CVE-2023-39325.
+ docs: https://go.dev/doc/devel/release#go1.20.minor
+
+ - version: 3.8.1
+ date: '2023-09-18'
+ notes:
+ - title: Upgrade Golang to 1.20.8
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This includes security fixes for CVE-2023-39318, CVE-2023-39319.
+ docs: https://go.dev/doc/devel/release#go1.20.minor
+
+ - version: 3.8.0
+ date: '2023-08-29'
+ notes:
+ - title: Ambassador Edge Stack will fail to run if a valid license is not present
+ type: change
+ body: >-
+ $productName$ will now require a valid non-expired license to run the product. If a valid license is not present or your clusters are not connected to and showing licensed in Ambassador Cloud, then $productName$ will refuse to startup. If you already have an enterprise license then you do not need to do anything so long as it is properly applied and not expired. Please view the license documentation page for more information on your license.
+ If you do not have an enterprise license for $productName$ then you can visit the quickstart guide to get setup with a free community license by signing into Ambassador Cloud and connecting your installation.
+ docs: tutorials/getting-started/
+ - title: Account for matchLabels when associating mappings with the same prefix to different Hosts
+ type: bugfix
+ body: >-
+ As of v2.2.2, if two mappings were associated with different Hosts through host
+ mappingSelector labels but share the same prefix, the labels were not taken into
+ account which would cause one Mapping to be correctly routed but the other not.
+
+ This change fixes this issue so that Mappings sharing the same prefix but associated
+ with different Hosts will be correctly routed.
+ docs: https://github.com/emissary-ingress/emissary/issues/4170
+ - title: Duplication of values when using multiple Headers/QueryParameters in Mappings
+ type: bugfix
+ body: >-
+ In previous versions, if multiple Headers/QueryParameters were used in a v3alpha1 mapping,
+ these values would duplicate and cause all the Headers/QueryParameters to have the same value.
+ This is no longer the case and the expected values for unique Headers/QueryParameters will apply.
+
+ This issue was only present in v3alpha1 Mappings. For users who may have this issue, please
+ be sure to re-apply any v3alpha1 Mappings in order to update the stored v2 Mapping and resolve the
+ issue.
+ docs: topics/using/headers/headers
+ - title: Ambassador Agent no longer collects Envoy metrics
+ type: change
+ body: >-
+ When the Ambassador agent is being used, it will no longer attempt to collect and report Envoy metrics.
+ In previous versions, $productName$ would always create an Envoy stats sink for the agent as long as the AMBASSADOR_GRPC_METRICS_SINK
+ environment variable was provided. This environment variable was hardcoded on the release manifests and has now been removed
+ and an Envoy stats sink for the agent is no longer created.
+ docs: topics/running/environment#ambassador_grpc_metrics_sink
+ - version: 3.7.2
+ date: '2023-07-25'
+ notes:
+ - title: Upgrade to Envoy 1.26.4
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.26.4 which includes a fixes for CVE-2023-35942, CVE-2023-35943, CVE-2023-35944.
+ docs: https://www.envoyproxy.io/docs/envoy/v1.26.1/version_history/v1.26/v1.26
+
+ - title: Shipped Helm chart v8.7.2
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.7.2.
+ docs: https://github.com/datawire/edge-stack/blob/rel/v3.7.2/charts/edge-stack/CHANGELOG.md
+
+ - version: 3.7.1
+ date: '2023-07-13'
+ notes:
+ - title: Upgrade to Envoy 1.26.3
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.26.3 which includes a fix for CVE-2023-35945.
+ docs: https://www.envoyproxy.io/docs/envoy/v1.26.1/version_history/v1.26/v1.26
+
+ - version: 3.7.0
+ date: '2023-06-20'
+ notes:
+ - title: Configurable Web Application Firewalls
+ type: feature
+ body: >-
+ $productName$ now provides configurable Web Application Firewalls (WAFs) that can be used to add additional security to your services by blocking dangerous requests. They can be configured globally or route by route. We have also published a ready to use set of rules to get you started and protected against the OWASP Top 10
+ vulnerabilities and adheres to PCI 6.6 requirements.
+ The published rule set will be updated and maintained regularly.
+ docs: howtos/web-application-firewalls/
+
+ - title: Upgrade to Envoy 1.26.1
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.26.1 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.26.1 Release Notes
+ docs: https://www.envoyproxy.io/docs/envoy/v1.26.1/version_history/v1.26/v1.26
+
+ - title: ExternalFilter - Add support for configuring TLS Settings
+ type: feature
+ body: >-
+ The ExternalFilter now supports configuring a CA certificate and/or client certificate via the new tlsConfig attribute. This allows $productName$ to communicate with the configured AuthService using custom TLS certificates signed by a different CA. It also allows the ExternalFilter to originate mTLS and have $productName$ present mTLS client certificates to the AuthService. Custom TLS certificates are provided as Kubernetes Secrets.
+ docs: topics/using/filters/external/#configuring-tls-settings
+
+ - version: 3.6.0
+ date: '2023-04-17'
+ notes:
+ - title: Deprecation of insteadOfRedirect.filters argument in FilterPolicy
+ type: change
+ body: >-
+ The insteadOfRedirect.filters field within the OAuth2 path-specific arguments has been deprecated and it will be fully removed in a future version of $productName$. Similiar behavior can
+ be accomplished using onDeny=continue and chaining a
+ fallback Filter to run.
+ docs: topics/using/filters/oauth2#oauth2-path-specific-arguments
+
+ - title: Upgrade to Envoy 1.25.4
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.25.4 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.25.4 Release Notes
+ docs: https://www.envoyproxy.io/docs/envoy/v1.25.4/version_history/v1.25/v1.25
+
+ - title: Shipped Helm chart v8.6.0
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.6.0.
+
+ - Add support for setting nodeSelector, tolerations and affinity on the Ambassador Agent. Thanks to Philip Panyukov.
+
+ - Use autoscaling API version based on Kubernetes version. Thanks to Elvind Valderhaug.
+
+ - Upgrade KubernetesEndpointResolver & ConsulResolver apiVersions to getambassador.io/v3alpha1
+
+ docs: https://github.com/emissary-ingress/emissary/blob/master/charts/emissary-ingress/CHANGELOG.md
+
+ - version: 3.5.2
+ date: "2023-04-05"
+ notes:
+ - title: Upgrade to Envoy 1.24.5
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.24.5. This update includes various security patches including CVE-2023-27487, CVE-2023-27491, CVE-2023-27492, CVE-2023-27493, CVE-2023-27488, and CVE-2023-27496. It also contains the dependency update for c-ares which was patched on top.
+
+ One notable item is that upstream header names and values are now validated according to RFC 7230, section 3.2. Users utilizing external filters should check whether their external service is forwarding headers containing forbidden characters
+ - title: Upgrade to Golang 1.20.3
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This includes security fixes for CVE-2023-24537, CVE-2023-24538, CVE-2023-24534, CVE-2023-24536.
+
+ - version: 3.5.1
+ date: '2023-02-24'
+ notes:
+ - title: Fix regression with ExternalFilter parsing port incorrectly
+ type: bugfix
+ body: >-
+ A regression with parsing the authService field of the ExternalFilter has been fixed. This would cause the ExternalFilter to fail without sending a request to the service causing a 403 response.
+
+ - title: Shipped Helm chart v8.5.1
+ type: bugfix
+ body: >-
+ Fix regression where the Module resource fails validation when setting the ambassador_id after upgrading to getambassador.io/v3alpha1.
+
+ Thanks to Pier.
+
+ docs: https://github.com/datawire/edge-stack
+
+ - version: 3.5.0
+ date: '2023-02-15'
+ notes:
+ - title: Upgraded to golang 1.20.1
+ type: security
+ body: >-
+ Upgraded to the latest release of Golang as part of our general dependency upgrade process. This includes
+ security fixes for CVE-2022-41725, CVE-2022-41723.
+
+ - title: TracingService support for native OpenTelemetry driver
+ type: feature
+ body: >-
+ In Envoy 1.24, experimental support for a native OpenTelemetry tracing driver was introduced that allows exporting spans in the otlp format. Many observability platforms accept that format and is the recommended replacement for the LightStep driver. $productName$ now supports setting the TracingService.spec.driver=opentelemetry to export traces in the otlp format.
+
+ Thanks to Paul for helping us get this tested and over the finish line!
+
+ - title: Switch to a non-blocking readiness check
+ type: feature
+ body: >-
+ The /ready endpoint used by $productName$ was using the Envoy admin port (8001 by default).This generates a problem during config reloads with large configs as the admin thread is blocking so the /ready endpoint can be very slow to answer (in the order of several seconds, even more).
+
+ $productName$ will now use a specific envoy listener that can answer /ready calls from an Envoy worker thread so the endpoint is always fast and it does not suffer from single threaded admin thread slowness on config reloads and other slow endpoints handled by the admin thread.
+
+ Configure the listener port using AMBASSADOR_READY_PORT and enable access log using AMBASSADOR_READY_LOG environment variables.
+
+ docs: https://www.getambassador.io/docs/edge-stack/latest/topics/running/environment/
+
+ - title: Fix envoy config generated when including port in Host.hostname
+ type: bugfix
+ body: >-
+ When wanting to expose traffic to clients on ports other than 80/443, users will set a port in the Host.hostname (eg.Host.hostname=example.com:8500). The config generated allowed matching on the :authority header. This worked in v1.Y series due to the way $productName$ was generating Envoy configuration under a single wild-card virtual_host and matching on :authority.
+
+ In v2.Y/v3.Y+, the way $productName$ generates Envoy configuration changed to address memory pressure and improve route lookup speed in Envoy. However, when including a port in the hostname, an incorrect configuration was generated with an sni match including the port. This caused incoming request to never match causing a 404 Not Found.This has been fixed and the correct envoy configuration is
+ being generated which restores existing behavior.
+
+ - title: Fix GRPC TLS support with ExternalFilter
+ type: bugfix
+ body: >-
+ Configuring an ExternalFilter to communicate using gRPC with TLS would fail due to $productName$ trying to connect via cleartext. This has been fixed so that setting ExternalFilter.spec.tls=true $productName$ will talk to the external filter using TLS.
+
+ If using self-signed certs see installing self-signed certificates on how to include it into the $productName$ deployment.
+
+ - title: Add support for resolving port names in Ingress resource
+ type: change
+ body: >-
+ Previously, specifying backend ports by name in Ingress was not supported and would result in defaulting to port 80. This allows $productName$ to now resolve port names for backend services. If the port number cannot be resolved by the name (e.g named port in the Service doesn't exist) then it will continue to default back
+ to port 80.
+
+ Thanks to Anton Ustyuzhanin!.
+ github:
+ - title: "#4809"
+ link: https://github.com/emissary-ingress/emissary/pull/4809
+
+ - title: Upgraded to Python 3.10
+ type: change
+ body: >-
+ Upgraded to Python 3.10 as part of continued investment in keeping dependencies updated.
+
+ - title: Upgraded base image to alpine-3.17
+ type: change
+ body: >-
+ Upgraded base image to alpine-3.17 as part of continued investment in keeping dependencies updated.
+
+ - title: Shipped Helm chart v8.5.0
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.5.0.
+
+ - Add support for configuring startupProbes on the $productName$ deployment.
+
+ - Allow setting pod and container security settings on the Ambassador Agent.
+
+ - Added new securityContext fields to the Redis and Agent helm charts, allowing users to further manage privilege and access control settings which can be used for tools such as PodSecurityPolicy.
+
+ - Added deprecation notice in the values.yaml file for podSecurityPolicy value because support has been removed in Kubernetes 1.25.
+
+ docs: https://github.com/datawire/edge-stack
+
+ - version: 3.4.1
+ date: '2023-02-07'
+ notes:
+ - title: Upgrade to Envoy 1.24.2
+ type: security
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.24.2. This update addresses the folowing notable items:
+
+ - Updates boringssl to address High CVE-2023-0286
+ - Updates c-ares dependency to address issue with cname wildcard dns resolution for upstream clusters
+
+ Users that are using $productName$ with Certificate Revocation List and allow external users to provide input should upgrade to ensure they are not vulnerable to CVE-2023-0286.
+
+ - version: 3.4.0
+ date: '2023-01-03'
+ notes:
+ - title: Upgrade to Envoy 1.24.1
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.24.1. Two notable changes were introduced:
+
+ First, the team at LightStep and the Envoy Maintainers have decided to no longer support the native LightStep tracing driver in favor of using the Open Telemetry driver. The code for the native Enovy LightStep driver has been removed from the Envoy code base. This means $productName$ will no longer support LightStep in the TracingService. The recommended upgrade path is to leverage a supported Tracing driver such as Zipkin and use the Open Telemetry Collector to collect and forward Observabity data to LightStep. A guide for this can be found here: Distributed Tracing with Open Telemetry and LightStep.
+
+ Second, a bug was fixed in Envoy 1.24 that changes how the upstream clusters distributed tracing span is named. Prior to Envoy 1.24 it would always set the span name to the cluster.name. The expected behavior from Envoy was that if provided an alt_stat_name then use it else fallback to cluster.name.
+
+ docs: https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.24/v1.24
+
+ - title: Re-add support for getambassador.io/v1
+ type: feature
+ body: >-
+ Support for the getambassador.io/v1 apiVersion has been re-introduced, in order to facilitate smoother migrations from $productName$ 1.y. Previously, in order to make migrations possible, an "unserved" v1 version was declared to Kubernetes, but was unsupported by $productName$. That unserved v1 could
+ cause an excess of errors to be logged by the Kubernetes Nodes (regardless of whether the installation was migrated from 1.y or was a fresh 2.y install).
+
+ It is still recommeded that `v3alpha1` be used but fully supporting v1 again should resolve these errors.
+ docs: https://github.com/emissary-ingress/emissary/pull/4055
+
+ - title: Add support for active health checking configuration.
+ type: feature
+ body: >-
+ It is now possible to configure active healhchecking for upstreams within a Mapping. If the upstream fails its configured health check then Envoy will mark the upstream as unhealthy and no longer send traffic to that upstream. Single pods within a group may can be marked as unhealthy. The healthy pods will continue to receive
+ traffic normally while the unhealthy pods will not receive any traffic until they recover by passing the health check.
+ docs: howtos/active-health-checking/
+
+ - title: Add environment variables to the healthcheck server.
+ type: feature
+ body: >-
+ The healthcheck server's bind address, bind port and IP family can now be configured using environment variables:
+
+ AMBASSADOR_HEALTHCHECK_BIND_ADDRESS: The address to bind the healthcheck server to.
+
+ AMBASSADOR_HEALTHCHECK_BIND_PORT: The port to bind the healthcheck server to.
+
+ AMBASSADOR_HEALTHCHECK_IP_FAMILY: The IP family to use for the healthcheck server.
+
+ This allows the healthcheck server to be configured to use IPv6-only k8s environments. (Thanks to Dmitry Golushko!).
+
+ - title: Added metrics for External Filters to the /metrics endpoint
+ type: feature
+ body: >-
+ $productName$ now tracks metrics for External Filters which includes responses approved/denied, the response codes returned as well as configuration and connection errors.
+ docs: topics/using/filters/external/#metrics
+
+ - title: Allow setting the OAuth2 client's session max idle time
+ type: feature
+ body: >-
+ When using the OAuth2 Filter, $productName$ creates a new session when submitting requests to the upstream backend server and sets a cookie containing the sessionID. This session has a limited lifetime before it expires or is extended, prompting the user to log back in.
+
+ This session idle length can now be controlled under a new field in the OAuth2 Filter, clientSessionMaxIdle, which controls how long the session will be active without activity before it is expired.
+ docs: topics/using/filters/oauth2
+
+ - title: Updated redis client to improve performance with Redis
+ type: change
+ body: >-
+ We have updated the client library used to communicate with Redis. The new client provides support for better connection handling and sharing and improved overall performance. As part of our update to
+ the new driver we reduced chattiness with Redis by taking advantage
+ of Pipelinig and Scripting features of Redis.
+
+ This means the AES_REDIS_EXPERIMENTAL_DRIVER_ENABLED flag is now a no-op and can be safely removed.
+
+ - title: Adopt stand alone Ambassador Agent
+ type: change
+ body: >-
+ Previously, the Agent used for communicating with Ambassador Cloud was bundled into $productName$. This tied it to the same release schedule as $productName$ and made it difficult to iterate on its feature set. It has now been extracted into its own repository and has its own release process and schedule.
+ docs: https://github.com/datawire/ambassador-agent
+
+ - title: Fix Filters not properly caching large jwks responses
+ type: bugfix
+ body: >-
+ In some cases, a Filter would fail to properly cache the response from the jwks endpoint due to the response being too large to cache. This would hurt performance and cause $productName$ to be rate-limited by the iDP. This has been fixed to accomodate iDP's that are configured to support multiple key sets thus returning a response that is larger than the typical default response from most iDP's.
+
+ - version: 3.3.1
+ date: '2022-12-08'
+ notes:
+ - title: Update Golang to 1.19.4
+ type: security
+ body: >-
+ Updated Golang to latest 1.19.4 patch release which contained two CVEs: CVE-2022-41720, CVE-2022-41717.
+
+ CVE-2022-41720 only affects Windows and $productName$ only ships on Linux. CVE-2022-41717 affects HTTP/2 servers that are exposed to external clients. By default, $productName$ exposes the endpoints for DevPortal, Authentication Service, and RateLimitService via Envoy. Envoy enforces a limit on request header size which mitigates the vulnerability.
+
+ - version: 3.3.0
+ date: '2022-11-02'
+ notes:
+ - title: Update Golang to 1.19.2
+ type: security
+ body: >-
+ Updated Golang to 1.19.2 to address the CVEs: CVE-2022-2879, CVE-2022-2880, CVE-2022-41715.
+
+ - title: Update golang.org/x/net
+ type: security
+ body: >-
+ Updated golang.org/x/net to address the CVE: CVE-2022-27664.
+
+ - title: Update golang.org/x/text
+ type: security
+ body: >-
+ Updated golang.org/x/text to address the CVE: CVE-2022-32149.
+
+ - title: Update JWT library
+ type: security
+ body: >-
+ Updated our JWT library from https://github.com/dgrijalva/jwt-go to https://github.com/golang-jwt/jwt in order to address spurious complaints about CVE-2020-26160. Edge Stack has never been affected by CVE-2020-26160.
+
+ - title: Fix regression in http to https redirects with AuthService
+ type: bugfix
+ body: >-
+ By default $productName$ adds routes for http to https redirection. When
+ an AuthService is applied in v2.Y of $productName$, Envoy would skip the
+ ext_authz call for non-tls http request and would perform the https
+ redirect. In Envoy 1.20+ the behavior has changed where Envoy will
+ always call the ext_authz filter and must be disabled on a per route
+ basis.
+ This new behavior change introduced a regression in v3.0 of
+ $productName$ when it was upgraded to Envoy 1.22. The http to https
+ redirection no longer works when an AuthService was applied. This fix
+ restores the previous behavior by disabling the ext_authz call on the
+ https redirect routes.
+ github:
+ - title: "#4620"
+ link: https://github.com/emissary-ingress/emissary/issues/4620
+
+ - title: Fix regression in host_redirects with AuthService
+ type: bugfix
+ body: >-
+ When an AuthService is applied in v2.Y of $productName$,
+ Envoy would skip the ext_authz call for all redirect routes and
+ would perform the redirect. In Envoy 1.20+ the behavior has changed
+ where Envoy will always call the ext_authz filter so it must be
+ disabled on a per route basis.
+ This new behavior change introduced a regression in v3.0 of
+ $productName$ when it was upgraded to Envoy 1.22. The host_redirect
+ would call an AuthService prior to redirect if applied. This fix
+ restores the previous behavior by disabling the ext_authz call on the
+ host_redirect routes.
+ github:
+ - title: "#4640"
+ link: https://github.com/emissary-ingress/emissary/issues/4640
+
+ - title: Propagate trace headers to http external filter
+ type: change
+ body: >-
+ Previously, tracing headers were not propagated to an ExternalFilter configured with proto: http. Now, adding supported tracing headers (b3, etc...) to the spec.allowed_request_headers will propagate them to the configured service.
+ docs: topics/using/filters/external/#tracing-header-propagation
+ github:
+ - title: "#3078"
+ link: https://github.com/datawire/apro/issues/3078
+
+ - version: 3.2.0
+ date: '2022-09-27'
+ notes:
+ - title: Update Golang to 1.19.1
+ type: security
+ body: >-
+ Updated Golang to 1.19.1 to address the CVEs: CVE-2022-27664, CVE-2022-32190.
+
+ - title: Add Post Logout Redirect URI support for Oauth2 Filter
+ type: feature
+ body: >-
+ You may now define (on supported IDPs) a postLogoutRedirectURI to your Oauth2 filter.
+ This will allow you to redirect to a specific URI upon logging out. However, in order to achieve this you must
+ define your IDP logout URL to https:{{host}}/.ambassador/oauth2/post-logout-redirect. Upon logout
+ $productName$ will redirect to the custom URI which will then redirect to the URI you have defined in postLogoutRedirectURI.
+ docs: topics/using/filters/oauth2
+
+ - title: Add support for Host resources using secrets from different namespaces
+ type: feature
+ body: >-
+ Previously the Host resource could only use secrets that are in the namespace as the
+ Host. The tlsSecret field in the Host has a new subfield namespace that will allow
+ the use of secrets from different namespaces.
+ docs: topics/running/tls/#bring-your-own-certificate
+
+ - title: Allow bypassing of EDS for manual endpoint insertion
+ type: feature
+ body: >-
+ Set AMBASSADOR_EDS_BYPASS to true to bypass EDS handling of endpoints and have endpoints be
+ inserted to clusters manually. This can help resolve with 503 UH caused by certification rotation relating to
+ a delay between EDS + CDS. The default is false.
+ docs: topics/running/environment/#ambassador_eds_bypass
+
+ - title: Add support for config change batch window before reconfiguring Envoy
+ type: feature
+ body: >-
+ The AMBASSADOR_RECONFIG_MAX_DELAY env var can be optionally set to batch changes for the specified
+ non-negative window period in seconds before doing an Envoy reconfiguration. Default is "1" if not set
+
+ - title: Allow setting custom_tags for traces
+ type: feature
+ body: >-
+ It is now possible to set custom_tags in the
+ TracingService. Trace tags can be set based on
+ literal values, environment variables, or request headers. The existing tag_headers field is now deperacated. If both tag_headers and custom_tags are set then tag_headers will be ignored.
+ (Thanks to Paul!)
+ docs: topics/running/services/tracing-service/
+
+ - title: Add failure_mode_deny option to the RateLimitService
+ type: feature
+ body: >-
+ By default, when Envoy is unable to communicate with the configured
+ RateLimitService then it will allow traffic through. The
+ RateLimitService resource now exposes the
+ failure_mode_deny
+ option. Set failure_mode_deny: true, then Envoy will
+ deny traffic when it is unable to communicate to the RateLimitService
+ returning a 500.
+
+ - title: Change to behavior for associating Hosts with Mappings and Listeners with Hosts
+ type: change
+ body: >-
+ Changes to label matching will change how Hosts are associated with Mappings and how Listeners are associated with Hosts. There was a bug with label
+ selectors that was causing resources that configure a selector to be incorrectly associated with more resources than intended.
+ If any single label from the selector was matched then the resources would be associated.
+ Now it has been updated to correctly only associate these resources if all labels required by
+ their selector are present. This brings the mappingSelector/selector fields in-line with how label selectors are used
+ in Kubernetes. To avoid unexpected behavior after the upgrade, add all labels that Hosts/Listeners have in their
+ mappingSelector/selector to Mappings/Hosts you want to associate with them. You can opt-out of the new behavior
+ by setting the environment variable DISABLE_STRICT_LABEL_SELECTORS to "true" (default: "false").
+ (Thanks to Filip Herceg and Joe Andaverde!).
+ docs: topics/running/environment/#disable_strict_label_selectors
+
+ - title: Envoy upgraded to 1.23.0
+ type: change
+ body: >-
+ The envoy version included in $productName$ has been upgraded from 1.22 to that latest release of 1.23.0. This provides $productName$ with the latest security patches, performances enhancments,and features offered by the envoy proxy.
+ docs: https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.23/v1.23.0
+
+ - title: Properly convert FilterPolicy and ExternalFilter between CRD versions
+ type: bugfix
+ body: >-
+ Previously, $productName$ would incorrectly include empty fields when converting a FilterPolicy or ExternalFilter between versions. This would cause undesired state to be persisted in k8s which would lead to validation issues when trying to kubectl apply the custom resource. This fixes these issues to ensure the correct data is being persisted and roundtripped properly between CRD versions.
+
+ - title: Correctly manage cluster names when service names are very long
+ type: bugfix
+ body: >-
+ Distinct services with names that are the same in the first forty characters will no longer be incorrectly mapped to the same cluster.
+ github:
+ - title: "#4354"
+ link: https://github.com/emissary-ingress/emissary/issues/4354
+
+ - title: Properly populate alt_stats_name for Tracing, Auth and RateLimit Services
+ type: bugfix
+ body: >-
+ Previously, setting the stats_name for the TracingService, RateLimitService
+ or the AuthService would have no affect because it was not being properly passed to the Envoy cluster
+ config. This has been fixed and the alt_stats_name field in the cluster config is now set correctly.
+ (Thanks to Paul!).
+
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : when using the
+ diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not
+ being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing
+ envoy metrics for the diagnostics user interface.
+
+ - title: TCPMappings use correct SNI configuration
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that uses SNI,
+ instead of using the hostname glob in the TCPMapping, uses the hostname glob
+ in the Host that the TLS termination configuration comes from.
+
+ - title: TCPMappings configure TLS termination without a Host resource
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that terminates TLS
+ must have a corresponding Host that it can take the TLS configuration from.
+ This was semi-intentional, but didn't make much sense. You can now use a
+ TLSContext without a Hostas in $productName$ 1.y releases, or a
+ Host with or without a TLSContext as in prior 2.y releases.
+
+ - title: TCPMappings and HTTP Hosts can coexist on Listeners that terminate TLS
+ type: bugfix
+ body: >-
+ Prior releases of $productName$ had the arbitrary limitation that a
+ TCPMapping cannot be used on the same port that HTTP is served on, even if
+ TLS+SNI would make this possible. $productName$ now allows TCPMappings to be
+ used on the same Listener port as HTTP Hosts, as long as that
+ Listener terminates TLS.
+
+ - version: 3.1.0
+ date: '2022-08-01'
+ notes:
+ - title: Add new Filter to support authenticating APIKey's
+ type: feature
+ body: >-
+ A new Filter has been added to support validating APIKey's on incoming requests.The new APIKeyFilter when applied with a FilterPolicy will check to
+ see if the incoming requests has a valid API Key in the request header. $productName$ uses Kubernetes Secret's to lookup valid keys for authorizing requests.
+ docs: topics/using/filters/apikeys
+ - title: Add support to watch for secrets with APIKey's
+ type: feature
+ body: >-
+ Emissary-ingress has been taught to watch for APIKey secrets when $productName$ is running and
+ makes them available to be used with the new APIKeyFilter.
+ - title: A new experimental Redis driver for use with the OAuth2 Filter
+ type: feature
+ body: >-
+ A new opt-in feature flag has been added that allows $productName$ to use a new Redis driver when storing state between requests for the OAuth2 Filter. The new driver has better connection pool handling, shares connections and supports the Redis RESP3 protocol.
+
+ Set AES_REDIS_EXPERIMENTAL_DRIVER_ENABLED=true to enable the experimental feature. Most of the standard Redis configuration fields (e.g.REDIS_*) can be used with the driver.
+ However, due to the drivers better connection handling the new driver no longer supports setting REDIS_SURGE_LIMIT_INTERVAL, REDIS_SURGE_LIMIT_AFTER, REDIS_SURGE_POOL_SIZE, REDIS_SURGE_POOL_DRAIN_INTERVAL and these will be ignored.
+
+ Note: Other $productName$ features such as the RateLimitService will continue to use the current
+ Redis driver and in future releases we plan to roll out the new driver for those features as well.
+ - title: Add support for injecting a valid synthetic RateLimitService
+ type: change
+ body: >-
+ If $productName$ is running then Emissary-ingress ensures that only a single RateLimitService is active. If a user doesn't provide one or provides an invalid one then a synthetic RateLimitService will be
+ injected. If the protocol_version field is not set or set to an invalid value then it will automatically get upgraded protocol_version: v3.
+
+ This matches the existing behavior that was introduced in $productName$ v3.0.0 for the AuthService. For new installs a valid RateLimitService will be added but this
+ change ensures a smooth upgrade from $productName$ to v2.3.Z to v3.Y for users who use the manifest in a GitOps scenario.
+ - title: Add Agent support for OpenAPI 2 contracts
+ type: feature
+ body: >-
+ The agent is now able to parse api contracts using swagger 2, and to convert them to OpenAPI 3, making them available for use in the dev portal.
+ - title: Default YAML enables the diagnostics interface from non-local clients on the admin service port
+ type: change
+ body: >-
+ In the standard published .yaml files, the Module resource enables serving remote client requests to the :8877/ambassador/v0/diag/ endpoint.The associated Helm chart release also now enables it by default.
+ - title: Add additional pprof endpoints
+ type: change
+ body: >-
+ Add pprof endpoints serving additional profiles including CPU profiles (/debug/pprof/profile) and tracing (/debug/pprof/trace). Also add additional endpoints serving the command line running (/debug/pprof/cmdline) and program counters (/debug/pprof/symbol) for the sake of completeness.
+ - title: Correct cookies for mixed HTTP/HTTPS OAuth2 origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter sets cookies for a protectedOrigin, it should set a cookie's "Secure" flag to true for https:// origins and false for http:// origins. However, for filters with multiple origins, it set the cookie's flag based on the first origin listen in the Filter, rather than the origin that the cookie is actually for.
+ - title: Correctly handle refresh tokens for OAuth2 filters with multiple origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter with multiple protectedOrigins needs to adjust the cookies for an active login (which only happens when using a refresh token), it
+ would erroneously redirect the web browser to the last origin listed, rather than returning to the original URL. This has been fixed.
+ - title: Correctly handle CORS and CORs preflight request within the OAuth2 Fitler known endpoints
+ type: bugfix
+ body: >-
+ Previously, the OAuth2 filter's known endpoints /.ambassador/oauth2/logout and /.ambassador/oauth2/multicookie did not understand CORS or CORS preflight request
+ which would cause the browser to reject the request. This has now been fixed and these endpoints will attach the appropriate CORS headers to the response.
+ - title: Fix regression in the agent for the metrics transfer.
+ type: bugfix
+ body: >-
+ A regression was introduced in 2.3.0 causing the agent to miss some of the metrics coming from emissary ingress before sending them to Ambassador cloud. This issue has been resolved to ensure that all the nodes composing the emissary ingress cluster are reporting properly.
+ - title: Handle long cluster names for injected acme-challenge route.
+ type: bugfix
+ body: >-
+ Previously, we would inject an upstream route for acme-challenge that was targeting the localhost auth service cluster. This route is injected to make Envoy configuration happy and the AuthService
+ that is shipped with $productName$ will handle it properly. However, if the cluster name is longer than 60 characters due to a long namespace, etc... then $productName$ will truncate it and make
+ sure it is unique. When this happens the name of the cluster assigned to the acme-challenge route would get out-of-sync and would introduce invalid Envoy configuration.
+
+ To avoid this $productName$ will now inject a route that returns a direct 404 response rather than pointing at an arbitrary cluster. This matches existing behavior and is a transparent
+ change to the user.
+
+ - title: Update Golang to 1.17.12
+ type: security
+ body: >-
+ Updated Golang to 1.17.12 to address the CVEs: CVE-2022-23806, CVE-2022-28327, CVE-2022-24675,
+ CVE-2022-24921, CVE-2022-23772.
+
+ - title: Update Curl to 7.80.0-r2
+ type: security
+ body: >-
+ Updated Curl to 7.80.0-r2 to address the CVEs: CVE-2022-32207, CVE-2022-27782, CVE-2022-27781,
+ CVE-2022-27780.
+
+ - title: Update openSSL-dev to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated openSSL-dev to 1.1.1q-r0 to address CVE-2022-2097.
+
+ - title: Update ncurses to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated ncurses to 1.1.1q-r0 to address CVE-2022-29458
+
+ - title: Upgrade jwt-go
+ type: security
+ body: >-
+ Upgrade jwt-go to latest commit to resolve CVE-2020-26160.
+
+ - version: 3.0.0
+ date: '2022-06-28'
+ notes:
+ - title: Upgrade to Envoy 1.22
+ type: change
+ body: >-
+ $productName$ has been upgraded to Envoy 1.22 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.22.0 Release Notes
+
+ This is a major jump in Envoy versions from the current of 1.17 in EdgeStack 2.X. Most of the changes are under the hood and allow $productName$ to adopt new features in the future. However, one major change that will effect users is the removal of V2 Transport Protocol support. You can find a transition guide here:
+
+ - title: Envoy V2 xDS Transport Protocol Support Removed
+ type: change
+ body: >-
+ Envoy removed support for V2 xDS Transport Protocol which means $productName$ now only supports the Envoy V3 xDS Transport Protocol.
+
+ User should first upgrade to $productName$ 2.3 prior to ensure that the LogServices and External Filters are working properly by setting protocol_version: "v3".
+
+ If protocol_version is not specified in 3.Y, the default value of v2 will cause an error to be posted and a static response will be returned. Therefore, you must set it to protocol_version: v3. If upgrading from a previous version, you will want to set it to v3 and ensure it is working before upgrading to Emissary-ingress 3.Y. The default value for protocol_version remains v2 in the getambassador.io/v3alpha1 CRD specifications to avoid making breaking changes outside of a CRD version change. Future versions of CRD's will deprecate it.
+ docs: topics/using/filters/external
+
+ - title: Initial HTTP/3 Downstream Support
+ type: feature
+ body: >-
+ With the upgrade to Envoy, $productName$ is now able to provide downstream support for HTTP/3. The initial implementation supports exposing http/3 endpoints on port `443`. Future version of $productName$ will seek to provide additional configuration to support more scenarios.
+
+ HTTP/3 uses the Quic protocol over UDP. Changes to your cloud provider provisioned Load Balancer will be required to support UDP traffic if using HTTP/3. External Load Balancers must serve traffic on port 443 because the alt-svc header is not configurable in the initial release of the feature.
+ docs: topics/running/http3
+
+ - version: 2.5.1
+ date: '2022-12-08'
+ notes:
+ - title: Update Golang to 1.19.4
+ type: security
+ body: >-
+ Updated Golang to latest 1.19.4 patch release which contained two CVEs: CVE-2022-41720, CVE-2022-41717.
+
+ CVE-2022-41720 only affects Windows and $productName$ only ships on Linux. CVE-2022-41717 affects HTTP/2 servers that are exposed to external clients. By default, $productName$ exposes the endpoints for DevPortal, Authentication Service, and RateLimitService via Envoy. Envoy enforces a limit on request header size which mitigates the vulnerability.
+
+ - version: 2.5.0
+ date: '2022-11-03'
+ notes:
+ - title: Propagate trace headers to http external filter
+ type: change
+ body: >-
+ Previously, tracing headers were not propagated to an ExternalFilter configured with
+ `proto: http`. Now, adding supported tracing headers (b3, etc...) to the
+ `spec.allowed_request_headers` will propagate them to the configured service.
+ github:
+ - title: "#3078"
+ link: https://github.com/datawire/apro/issues/3078
+
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : then when using the
+ diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not
+ being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing
+ envoy metrics for the diagnostics user interface.
+
+ - title: Bump Golang to 1.19.2
+ type: security
+ body: >-
+ Bump Go from 1.17.12 to 1.19.2. This is to keep the Go version current.
+
+ - version: 2.4.2
+ date: '2022-10-10'
+ notes:
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : then when using the diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing envoy metrics for the diagnostics user interface.
+
+ - title: Backport fixes for handling synthetic auth services
+ type: bugfix
+ body: >-
+ The synthetic AuthService didn't correctly handle AmbassadorID, which was fixed in version 3.1 of $productName$.The fix has been backported to make sure the AuthService is handled correctly during upgrades.
+
+ - version: 2.4.1
+ date: '2022-09-27'
+ notes:
+ - title: Addressing release issue with 2.4.0
+ type: bugfix
+ body: >-
+ During the $productName$ 2.4.0 release process there was an issue with the Emissary binary. This has been patched and resolved.
+
+ - version: 2.4.0
+ date: '2022-09-19'
+ notes:
+ - title: Add support for Host resources using secrets from different namespaces
+ type: feature
+ body: >-
+ Previously the Host resource could only use secrets that are in the namespace as the
+ Host. The tlsSecret field in the Host has a new subfield namespace that will allow
+ the use of secrets from different namespaces.
+ docs: topics/running/tls/#bring-your-own-certificate
+
+ - title: Allow bypassing of EDS for manual endpoint insertion
+ type: change
+ body: >-
+ Set AMBASSADOR_EDS_BYPASS to true to bypass EDS handling of endpoints and have endpoints be
+ inserted to clusters manually. This can help resolve with 503 UH caused by certification rotation relating to
+ a delay between EDS + CDS. The default is false.
+ docs: topics/running/environment/#ambassador_eds_bypass
+
+ - title: Properly convert FilterPolicy and ExternalFilter between CRD versions
+ type: bugfix
+ body: >-
+ Previously, $productName$ would incorrectly include empty fields when converting a FilterPolicy or ExternalFilter between versions. This would cause undesired state to be persisted in k8s which would lead to validation issues when trying to kubectl apply the custom resource. This fixes these issues to ensure the correct data is being persisted and roundtripped properly between CRD versions.
+
+ - title: Properly populate alt_stats_name for Tracing, Auth and RateLimit Services
+ type: bugfix
+ body: >-
+ Previously, setting the stats_name for the TracingService, RateLimitService
+ or the AuthService would have no affect because it was not being properly passed to the Envoy cluster
+ config. This has been fixed and the alt_stats_name field in the cluster config is now set correctly.
+ (Thanks to Paul!)
+
+ - title: Diagnostics stats properly handles parsing envoy metrics with colons
+ type: bugfix
+ body: >-
+ If a Host or TLSContext contained a hostname with a : when using the
+ diagnostics endpoints ambassador/v0/diagd then an error would be thrown due to the parsing logic not
+ being able to handle the extra colon. This has been fixed and $productName$ will not throw an error when parsing
+ envoy metrics for the diagnostics user interface.
+
+ - title: TCPMappings use correct SNI configuration
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that uses SNI,
+ instead of using the hostname glob in the TCPMapping, uses the hostname glob
+ in the Host that the TLS termination configuration comes from.
+
+ - title: TCPMappings configure TLS termination without a Host resource
+ type: bugfix
+ body: >-
+ $productName$ 2.0.0 introduced a bug where a TCPMapping that terminates TLS
+ must have a corresponding Host that it can take the TLS configuration from.
+ This was semi-intentional, but didn't make much sense. You can now use a
+ TLSContext without a Hostas in $productName$ 1.y releases, or a
+ Host with or without a TLSContext as in prior 2.y releases.
+
+ - title: TCPMappings and HTTP Hosts can coexist on Listeners that terminate TLS
+ type: bugfix
+ body: >-
+ Prior releases of $productName$ had the arbitrary limitation that a
+ TCPMapping cannot be used on the same port that HTTP is served on, even if
+ TLS+SNI would make this possible. $productName$ now allows TCPMappings to be
+ used on the same Listener port as HTTP Hosts, as long as that
+ Listener terminates TLS.
+
+ - version: 2.3.2
+ date: '2022-08-01'
+ notes:
+ - title: Correct cookies for mixed HTTP/HTTPS OAuth2 origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter sets cookies for a protectedOrigin, it
+ should set a cookie's "Secure" flag to true for https:// origins and false
+ for http:// origins. However, for filters with multiple origins, it set the
+ cookie's flag based on the first origin listen in the Filter, rather than the origin that
+ the cookie is actually for.
+
+ - title: Correctly handle refresh tokens for OAuth2 filters with multiple origins
+ type: bugfix
+ body: >-
+ When an OAuth2 filter with multiple protectedOrigins needs to
+ adjust the cookies for an active login (which only happens when using a refresh token), it
+ would erroneously redirect the web browser to the last origin listed, rather than
+ returning to the original URL. This has been fixed.
+
+ - title: Correctly handle CORS and CORs preflight request within the OAuth2 Fitler known endpoints
+ type: bugfix
+ body: >-
+ Previously, the OAuth2 filter's known endpoints /.ambassador/oauth2/logout
+ and /.ambassador/oauth2/multicookie did not understand CORS or CORS preflight request
+ which would cause the browser to reject the request. This has now been fixed and these endpoints will
+ attach the appropriate CORS headers to the response.
+
+ - title: Fix regression in the agent for the metrics transfer.
+ type: bugfix
+ body: >-
+ A regression was introduced in 2.3.0 causing the agent to miss some of the metrics coming from
+ emissary ingress before sending them to Ambassador cloud. This issue has been resolved to ensure
+ that all the nodes composing the emissary ingress cluster are reporting properly.
+
+ - title: Update Golang to 1.17.12
+ type: security
+ body: >-
+ Updated Golang to 1.17.12 to address the CVEs: CVE-2022-23806, CVE-2022-28327, CVE-2022-24675,
+ CVE-2022-24921, CVE-2022-23772.
+
+ - title: Update Curl to 7.80.0-r2
+ type: security
+ body: >-
+ Updated Curl to 7.80.0-r2 to address the CVEs: CVE-2022-32207, CVE-2022-27782, CVE-2022-27781,
+ CVE-2022-27780.
+
+ - title: Update openSSL-dev to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated openSSL-dev to 1.1.1q-r0 to address CVE-2022-2097.
+
+ - title: Update ncurses to 1.1.1q-r0
+ type: security
+ body: >-
+ Updated ncurses to 1.1.1q-r0 to address CVE-2022-29458
+
+ - title: Upgrade jwt-go
+ type: security
+ body: >-
+ Upgrade jwt-go to latest commit to resolve CVE-2020-26160.
+
+ - version: 2.3.1
+ date: '2022-06-10'
+ notes:
+ - title: Fix regression in tracing service config
+ type: bugfix
+ body: >-
+ A regression was introduced in 2.3.0 that leaked zipkin default config fields into the configuration
+ for the other drivers (lightstep, etc...). This caused $productName$ to crash on startup. This issue has been resolved
+ to ensure that the defaults are only applied when driver is zipkin
+ docs: https://github.com/emissary-ingress/emissary/issues/4267
+
+ - title: Envoy security updates
+ type: security
+ body: >-
+ We have backported patches from the Envoy 1.19.5 security update to $productName$'s
+ 1.17-based Envoy, addressing CVE-2022-29224 and CVE-2022-29225. $productName$ is not
+ affected by CVE-2022-29226, CVE-2022-29227, or CVE-2022-29228; as it does not support internal
+ redirects, and does not use Envoy's built-in OAuth2 filter.
+ docs: https://groups.google.com/g/envoy-announce/c/8nP3Kn4jV7k
+
+ - version: 2.3.0
+ date: '2022-06-06'
+ notes:
+ - title: Remove unused packages
+ type: security
+ body: >-
+ Completely remove gdbm, pip, smtplib, and sqlite packages, as they are unused.
+
+ - title: CORS now happens before auth
+ type: bugfix
+ body: >-
+ When CORS is specified (either in a Mapping or in the Ambassador
+ Module), CORS processing will happen before authentication. This corrects a
+ problem where XHR to authenticated endpoints would fail.
+
+ - title: Correctly handle caching of Mappings with the same name in different namespaces
+ type: bugfix
+ body: >-
+ In 2.x releases of $productName$ when there are multiple Mappings that have the same
+ metadata.name across multiple namespaces, their old config would not properly be removed
+ from the cache when their config was updated. This resulted in an inability to update configuration
+ for groups of Mappings that share the same name until the $productName$ pods restarted.
+
+ - title: Fix support for Zipkin API-v1 with Envoy xDS-v3
+ type: bugfix
+ body: >-
+ It is now possible for a TracingService to specify
+ collector_endpoint_version: HTTP_JSON_V1 when using xDS v3 to configure Envoy
+ (which has been the default since $productName$ 1.14.0). The HTTP_JSON_V1
+ value configures Envoy to speak to Zipkin using Zipkin's old API-v1, while the
+ HTTP_JSON value configures Envoy to speak to Zipkin using Zipkin's new
+ API-v2. In previous versions of $productName$ it was only possible to use
+ HTTP_JSON_V1 when explicitly setting the
+ AMBASSADOR_ENVOY_API_VERSION=V2 environment variable to force use of xDS v2
+ to configure Envoy.
+ docs: topics/running/services/tracing-service/
+
+ - title: Added Support for transport protocol v3 in External Filters
+ type: feature
+ body: >-
+ External Filters can now make use of the v3 transport protocol. In addtion to the support for the v3 transport protocol, the default AuthService installed with $productName$ will now only operate with transport protocol v3. In order to support existing External Filters using v2, $productName$ will automatically translate
+ v2 to the new default of v3. Any External Filters will be assumed to be using transport protocol v2 and will use the automatic conversion to v3 unless the new protocol_version field on the External Filter is explicitly set to v3.
+ docs: topics/using/filters/external
+
+ - title: Allow setting propagation modes for Lightstep tracing
+ type: feature
+ body: >-
+ It is now possible to set propagation_modes in the
+ TracingService config when using lightstep as the driver.
+ (Thanks to Paul!)
+ docs: topics/running/services/tracing-service/
+ github:
+ - title: '#4179'
+ link: https://github.com/emissary-ingress/emissary/pull/4179
+
+ - title: Added Support for Certificate Revocation Lists
+ type: feature
+ body: >-
+ $productName$ now supports Envoy's Certificate Revocation lists.
+ This allows users to specify a list of certificates that $productName$ should reject even if the certificate itself is otherwise valid.
+ docs: topics/running/tls
+
+ - title: Added support for the LogService v3 transport protocol
+ type: feature
+ body: >-
+ Previously, a LogService would always have $productName$ communicate with the
+ external log service using the envoy.service.accesslog.v2.AccessLogService
+ API. It is now possible for the LogService to specify
+ protocol_version: v3 to use the newer
+ envoy.service.accesslog.v3.AccessLogService API instead. This functionality
+ is not available if you set the AMBASSADOR_ENVOY_API_VERSION=V2 environment
+ variable.
+ docs: topics/running/services/log-service/
+
+ - title: Improved performance processing OAuth2 Filters
+ type: change
+ body: >-
+ When each OAuth2 Filter that references a Kubernetes secret is loaded, $productName$ previously needed to communicate with the API server to request and validate that secret before loading the next Filter. To improve performance, $productName$ will now load and validate all secrets required by OAuth2 Filters at once prior to loading the filters.
+
+ - title: Deprecated v2 transport protocol for External Filters and AuthServices
+ type: change
+ body: >-
+ A future release of $productName$ will remove support for the now deprecated v2 transport protocol in both AuthServices as well as External Filters. Migrating Existing External Filters from v2 to v3
+ is simple and and example can be found on the External Filter page. This change only impacts gRPC External Filters. HTTP External Filters are unaffected by this change.
+ docs: topics/using/filters/external
+
+ - version: 2.2.2
+ date: '2022-02-25'
+ notes:
+ - title: TLS Secret validation is now opt-in
+ type: change
+ body: >-
+ You may now choose to enable TLS Secret validation by setting the
+ AMBASSADOR_FORCE_SECRET_VALIDATION=true environment variable. The default configuration does not
+ enforce secret validation.
+ docs: topics/running/tls#certificates-and-secrets
+
+ - title: Correctly validate EC (Elliptic Curve) Private Keys
+ type: bugfix
+ body: >-
+ Kubernetes Secrets that should contain an EC (Elliptic Curve) TLS Private Key are now properly validated.
+ github:
+ - title: '#4134'
+ link: https://github.com/emissary-ingress/emissary/issues/4134
+ docs: topics/running/tls#certificates-and-secrets
+
+ - version: 2.2.1
+ date: '2022-02-22'
+ notes:
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Envoy security updates
+ type: security
+ body: >-
+ Upgraded Envoy to address security vulnerabilities CVE-2021-43824, CVE-2021-43825, CVE-2021-43826,
+ CVE-2022-21654, and CVE-2022-21655.
+ docs: https://groups.google.com/g/envoy-announce/c/bIUgEDKHl4g
+ - title: Correctly support canceling rollouts
+ type: bugfix
+ body: >-
+ The Ambassador Agent now correctly supports requests to cancel a rollout.
+ docs: ../../argo/latest/howtos/manage-rollouts-using-cloud
+
+ - version: 2.2.0
+ date: '2022-02-10'
+ notes:
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Ambassador Edge Stack will watch for Cloud Connect Tokens
+ type: change
+ body: >-
+ $productName$ will now watch for ConfigMap or Secret resources specified by the
+ AGENT_CONFIG_RESOURCE_NAME environment variable in order to allow all
+ components (and not only the Ambassador Agent) to authenticate requests to
+ Ambassador Cloud.
+ image: ./v2.2.0-cloud.png
+
+ - title: Update Alpine and libraries
+ type: security
+ body: >-
+ $productName$ has updated Alpine to 3.15, and Python and Go dependencies
+ to their latest compatible versions, to incorporate numerous security patches.
+
+ - title: Support a log-level metric
+ type: feature
+ body: >-
+ $productName$ now supports the metric ambassador_log_level{label="debug"}
+ which will be set to 1 if debug logging is enabled for the running Emissary
+ instance, or to 0 if not. This can help to be sure that a running production
+ instance was not actually left doing debugging logging, for example.
+ (Thanks to Fabrice!)
+ github:
+ - title: '#3906'
+ link: https://github.com/emissary-ingress/emissary/issues/3906
+ docs: topics/running/statistics/8877-metrics/
+
+ - title: Envoy configuration % escaping
+ type: feature
+ body: >-
+ $productName$ is now leveraging a new Envoy Proxy patch that allows Envoy to accept escaped
+ '%' characters in its configuration. This means that error_response_overrides and other
+ custom user content can now contain '%' symbols escaped as '%%'.
+ docs: topics/running/custom-error-responses
+ github:
+ - title: 'DW Envoy: 74'
+ link: https://github.com/datawire/envoy/pull/74
+ - title: 'Upstream Envoy: 19383'
+ link: https://github.com/envoyproxy/envoy/pull/19383
+ image: ./v2.2.0-percent-escape.png
+
+ - title: Stream metrics from Envoy to Ambassador Cloud
+ type: feature
+ body: >-
+ Support for streaming Envoy metrics about the clusters to Ambassador Cloud.
+ github:
+ - title: '#4053'
+ link: https://github.com/emissary-ingress/emissary/pull/4053
+ docs: https://github.com/emissary-ingress/emissary/pull/4053
+
+ - title: Support received commands to pause, continue and abort a Rollout via Agent directives
+ type: feature
+ body: >-
+ The Ambassador agent now receives commands to manipulate Rollouts (pause, continue, and
+ abort are currently supported) via directives and executes them in the cluster. A report
+ is sent to Ambassador Cloud including the command ID, whether it ran successfully, and
+ an error message in case there was any.
+ github:
+ - title: '#4040'
+ link: https://github.com/emissary-ingress/emissary/pull/4040
+ docs: https://github.com/emissary-ingress/emissary/pull/4040
+
+ - title: Validate certificates in TLS Secrets
+ type: bugfix
+ body: >-
+ Kubernetes Secrets that should contain TLS certificates are now validated before being
+ accepted for configuration. A Secret that contains an invalid TLS certificate will be logged
+ as an invalid resource.
+ github:
+ - title: '#3821'
+ link: https://github.com/emissary-ingress/emissary/issues/3821
+ docs: ../topics/running/tls
+ image: ./v2.2.0-tls-cert-validation.png
+
+ - title: Devportal support for using API server definitions from OpenAPI docs
+ type: feature
+ body: >-
+ You can now set preserve_servers in Ambassador Edge Stack's
+ DevPortal resource to configure the DevPortal to use server definitions from
+ the OpenAPI document when displaying connection information for services in the DevPortal.
+ docs: topics/using/dev-portal/
+
+ - version: 2.1.2
+ date: '2022-01-25'
+ notes:
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Docker BuildKit always used for builds
+ type: change
+ body: >-
+ Docker BuildKit is enabled for all Emissary builds. Additionally, the Go
+ build cache is fully enabled when building images, speeding up repeated builds.
+ docs: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md
+
+ - title: Fix OAuth2 Filter jwtAssertion
+ type: bugfix
+ body: >-
+ In $productName$ 2.1.0 and 2.1.1, an OAuth2 Filter with
+ clientAuthentication.method=jwtAssertion would not function correctly as it
+ would fail to select the signing-method-appropriate function to parse the private key.
+ docs: topics/using/filters/oauth2
+ image: ./v2.1.2-filter-jwtassertion.png
+
+ - title: Fix ifRequestHeader without a value
+ type: bugfix
+ body: >-
+ In $productName$ 2.1.0 and 2.1.1, an ifRequestHeader selector (in a
+ FilterPolicy, OAuth2 Filter useSessionCookies, or OAuth2 Filter
+ insteadOfRedirect) without a value or valueRegex
+ would erroneously behave as if valueRegex='^$', rather than performing a
+ simple presence check.
+ docs: custom-resources/getambassador/v3alpha1/filterpolicy
+
+ - title: Fix support for for v2 Mappings with CORS
+ type: bugfix
+ body: >-
+ Ambassador Edge Stack 2.1.1 generated invalid Envoy configuration for
+ getambassador.io/v2Mappings that set
+ spec.cors.origins to a string rather than a list of strings; this has been
+ fixed, and these Mappings should once again function correctly.
+ docs: topics/using/cors/#the-cors-attribute
+ image: ./v2.1.2-mapping-cors.png
+
+ - title: Correctly handle canary Mapping weights when reconfiguring
+ type: bugfix
+ body: >-
+ Changes to the weight of Mapping in a canary group
+ will now always be correctly managed during reconfiguration; such changes could
+ have been missed in earlier releases.
+ docs: topics/using/canary/#the-weight-attribute
+
+ - title: Correctly handle solitary Mappings with explicit weights
+ type: bugfix
+ body: >-
+ A Mapping that is not part of a canary group, but that has a
+ weight less than 100, will be correctly configured to receive all
+ traffic as if the weight were 100.
+ docs: topics/using/canary/#the-weight-attribute
+ image: ./v2.1.2-mapping-less-weighted.png
+
+ - title: Correctly handle empty rewrite in a Mapping
+ type: bugfix
+ body: >-
+ Using rewrite: "" in a Mapping is correctly handled
+ to mean "do not rewrite the path at all".
+ docs: topics/using/rewrites
+ image: ./v2.1.2-mapping-no-rewrite.png
+
+ - title: Correctly use Mappings with host redirects
+ type: bugfix
+ body: >-
+ Any Mapping that uses the host_redirect field is now properly discovered and used. Thanks
+ to Gabriel Féron for contributing this bugfix!
+ github:
+ - title: '#3709'
+ link: https://github.com/emissary-ingress/emissary/issues/3709
+ docs: https://github.com/emissary-ingress/emissary/issues/3709
+
+ - title: Correctly handle DNS wildcards when associating Hosts and Mappings
+ type: bugfix
+ body: >-
+ Mappings with DNS wildcard hostname will now be correctly
+ matched with Hosts. Previously, the case where both the Host
+ and the Mapping use DNS wildcards for their hostnames could sometimes
+ not correctly match when they should have.
+ docs: howtos/configure-communications/
+ image: ./v2.1.2-host-mapping-matching.png
+
+ - title: Fix overriding global settings for adding or removing headers
+ type: bugfix
+ body: >-
+ If the ambassadorModule sets a global default for
+ add_request_headers, add_response_headers,
+ remove_request_headers, or remove_response_headers, it is often
+ desirable to be able to turn off that setting locally for a specific Mapping.
+ For several releases this has not been possible for Mappings that are native
+ Kubernetes resources (as opposed to annotations), as an empty value ("mask the global
+ default") was erroneously considered to be equivalent to unset ("inherit the global
+ default"). This is now fixed.
+ docs: topics/using/defaults/
+
+ - title: Fix empty error_response_override bodies
+ type: bugfix
+ body: >-
+ It is now possible to set a Mapping
+ spec.error_response_overridesbody.text_format to an empty
+ string or body.json_format to an empty dict. Previously, this was possible
+ for annotations but not for native Kubernetes resources.
+ docs: topics/running/custom-error-responses/
+
+ - title: Annotation conversion and validation
+ type: bugfix
+ body: >-
+ Resources that exist as getambassador.io/config annotations rather than as
+ native Kubernetes resources are now validated and internally converted to v3alpha1 and,
+ the same as native Kubernetes resources.
+ image: ./v2.1.2-annotations.png
+
+ - title: Validation error reporting
+ type: bugfix
+ body: >-
+ Resource validation errors are now reported more consistently; it was the case that in
+ some situations a validation error would not be reported.
+
+ - version: 2.1.1
+ date: '2022-01-14'
+ notes:
+ - title: Not recommended; upgrade to 2.1.2 instead
+ type: change
+ isHeadline: true
+ body: >-
+ Ambassador Edge Stack 2.1.1 is not recommended; upgrade to 2.1.2 instead.
+
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Fix discovery of Filters, FilterPolicies, and RateLimits
+ type: bugfix
+ body: >-
+ In Edge Stack 2.1.0, it erroneously ignored Filters,
+ FilterPolicies, and RateLimits that were created as
+ v3alpha1 (but correctly paid attention to them if they were created as
+ v2 or older). This is fixed; it will now correctly pay attention to both API
+ versions.
+ github:
+ - title: '#3982'
+ link: https://github.com/emissary-ingress/emissary/issues/3982
+
+ - version: 2.1.0
+ date: '2021-12-16'
+ notes:
+ - title: Not recommended; upgrade to 2.1.2 instead
+ type: change
+ isHeadline: true
+ body: >-
+ Ambassador Edge Stack 2.1.0 is not recommended; upgrade to 2.1.2 instead.
+
+ - title: Envoy V2 API deprecation
+ type: change
+ body: >-
+ Support for the Envoy V2 API is deprecated as of $productName$ v2.1, and will be removed in $productName$
+ v3.0. The AMBASSADOR_ENVOY_API_VERSION environment variable will be removed at the same
+ time. Only the Envoy V3 API will be supported (this has been the default since $productName$ v1.14.0).
+
+ - title: Smoother migrations with support for getambassador.io/v2 CRDs
+ type: feature
+ body: >-
+ $productName$ supports getambassador.io/v2 CRDs, to simplify migration from $productName$
+ 1.X. Note: it is important to read the migration
+ documentation before starting migration.
+ docs: topics/install/migration-matrix
+ image: ./v2.1.0-smoother-migration.png
+
+ - title: Ambassador Edge Stack CRDs are fully validated
+ type: change
+ body: >-
+ The $productName$ CRDs (Filter, FilterPolicy, and RateLimit)
+ will now be validated for correct syntax by Kubernetes itself. This means that kubectl apply
+ will reject invalid CRDs before they are actually applied, preventing them from causing errors.
+ image: ./v2.1.0-edge-stack-validation.png
+
+ - title: Correctly handle all changing canary configurations
+ type: bugfix
+ body: >-
+ The incremental reconfiguration cache could miss some updates when multiple
+ Mappings had the same prefix ("canary"ing multiple
+ Mappings together). This has been corrected, so that all such
+ updates correctly take effect.
+ github:
+ - title: '#3945'
+ link: https://github.com/emissary-ingress/emissary/issues/3945
+ docs: https://github.com/emissary-ingress/emissary/issues/3945
+ image: ./v2.1.0-canary.png
+
+ - title: Secrets used for ACME private keys will not log errors
+ type: bugfix
+ body: >-
+ When using Kubernetes Secrets to store ACME private keys (as the Edge Stack
+ ACME client does), an error would always be logged about the Secret not being
+ present, even though it was present, and everything was working correctly.
+ This error is no longer logged.
+
+ - title: When using gzip, upstreams will no longer receive encoded data
+ type: bugfix
+ body: >-
+ When using gzip compression, upstream services will no longer receive compressed
+ data. This bug was introduced in 1.14.0. The fix restores the default behavior of
+ not sending compressed data to upstream services.
+ github:
+ - title: '#3818'
+ link: https://github.com/emissary-ingress/emissary/issues/3818
+ docs: https://github.com/emissary-ingress/emissary/issues/3818
+ image: ./v2.1.0-gzip-enabled.png
+
+ - title: Update to busybox 1.34.1
+ type: security
+ body: >-
+ Update to busybox 1.34.1 to resolve CVE-2021-28831, CVE-2021-42378,
+ CVE-2021-42379, CVE-2021-42380, CVE-2021-42381, CVE-2021-42382, CVE-2021-42383,
+ CVE-2021-42384, CVE-2021-42385, and CVE-2021-42386.
+
+ - title: Update Python dependencies
+ type: security
+ body: >-
+ Update Python dependencies to resolve CVE-2020-28493 (jinja2), CVE-2021-28363
+ (urllib3), and CVE-2021-33503 (urllib3).
+
+ - title: Remove test-only code from the built image
+ type: security
+ body: >-
+ Previous built images included some Python packages used only for test. These
+ have now been removed, resolving CVE-2020-29651.
+
+ - version: 2.0.5
+ date: '20211109'
+ notes:
+ - title: More aggressive HTTP cache behavior
+ type: change
+ body: >-
+ When Ambassador Edge Stack makes a cacheable internal request (such as fetching the JWKS
+ endpoint for a JWTFilter), if a cache-miss occurs but a request
+ for that resource is already in-flight, then instead of performing a second request in
+ parallel, it will now wait for the first request to finish and (if the response is
+ cacheable) use that response. If the response turns out to be non-cacheable, then it will
+ proceed to make the second request. This avoids the situation where if a cache entry
+ expires during a moment with high number of concurrent requests, then Edge Stack creates a
+ deluge of concurrent requests to the resource when one aught to have sufficed; this allows
+ the result to be returned more quickly while putting less load on the remote resource.
+ However, if the response turns out to be non-cacheable, then this does effectively
+ serialize requests, increasing the latency for concurrent requests.
+ image: ./v2.0.5-cache-change.png
+
+ - title: AuthService circuit breakers
+ type: feature
+ body: >-
+ It is now possible to set the circuit_breakers for AuthServices,
+ exactly the same as for Mappings and TCPMappings. This makes it
+ possible to configure your AuthService to be able to handle more than 1024
+ concurrent requests.
+ docs: topics/running/services/auth-service/
+ image: ./v2.0.5-auth-circuit-breaker.png
+
+ - title: More accurate durations in the logs
+ type: bugfix
+ body: >-
+ When Ambassador Edge Stack completes an internal request (such as fetching the JWKS
+ endpoint for a JWTFilter) it logs (at the info log
+ level) how long the request took. Previously, the duration logged was how long it took to
+ receive the response header, and did not count the time it takes to receive the entire
+ response body; now it properly times the entire thing. Additionally, it now separately
+ logs the "total duration" and the "networking duration", in order to make it possible to
+ identify when a request was delayed waiting for other requests to finish.
+
+ - title: Improved validity checking for error response overrides
+ type: bugfix
+ body: >-
+ Any token delimited by '%' is now validated agains a whitelist of valid
+ Envoy command operators. Any mapping containing an error_response_overrides
+ section with invalid command operators will be discarded.
+ docs: topics/running/custom-error-responses
+
+ - title: mappingSelector is now correctly supported in the Host CRD
+ type: bugfix
+ body: >-
+ The Host CRD now correctly supports the mappingSelector
+ element, as documented. As a transition aid, selector is a synonym for
+ mappingSelector; a future version of $productName$ will remove the
+ selector element.
+ github:
+ - title: '#3902'
+ link: https://github.com/emissary-ingress/emissary/issues/3902
+ docs: https://github.com/emissary-ingress/emissary/issues/3902
+ image: ./v2.0.5-mappingselector.png
+
+ - version: 2.0.4
+ date: '2021-10-19'
+ notes:
+ - title: General availability!
+ type: feature
+ body: >-
+ We're pleased to introduce $productName$ 2.0.4 for general availability! The
+ 2.X family introduces a number of changes to allow $productName$ to more
+ gracefully handle larger installations, reduce global configuration to better
+ handle multitenant or multiorganizational installations, reduce memory footprint, and
+ improve performance. We welcome feedback!! Join us on
+ Slack and let us know what you think.
+ isHeadline: true
+ docs: about/changes-2.x
+ image: ./edge-stack-GA.png
+
+ - title: API version getambassador.io/v3alpha1
+ type: change
+ body: >-
+ The x.getambassador.io/v3alpha1 API version has become the
+ getambassador.io/v3alpha1 API version. The Ambassador-
+ prefixes from x.getambassador.io/v3alpha1 resources have been
+ removed for ease of migration. Note that getambassador.io/v3alpha1
+ is the only supported API version for 2.0.4 — full support for
+ getambassador.io/v2 will arrive soon in a later 2.X version.
+ docs: about/changes-2.x
+ image: ./v2.0.4-v3alpha1.png
+
+ - title: Support for Kubernetes 1.22
+ type: feature
+ body: >-
+ The getambassador.io/v3alpha1 API version and the published chart
+ and manifests have been updated to support Kubernetes 1.22. Thanks to
+ Mohit Sharma for contributions to
+ this feature!
+ docs: about/changes-2.x
+ image: ./v2.0.4-k8s-1.22.png
+
+ - title: Mappings support configuring strict or logical DNS
+ type: feature
+ body: >-
+ You can now set dns_type between strict_dns and
+ logical_dns in a Mapping to configure the Service
+ Discovery Type.
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+ image: ./v2.0.4-mapping-dns-type.png
+
+ - title: Mappings support controlling DNS refresh with DNS TTL
+ type: feature
+ body: >-
+ You can now set respect_dns_ttl to true to force the
+ DNS refresh rate for a Mapping to be set to the record's TTL
+ obtained from DNS resolution.
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+
+ - title: Support configuring upstream buffer sizes
+ type: feature
+ body: >-
+ You can now set buffer_limit_bytes in the ambassador
+ Module to to change the size of the upstream read and write buffers.
+ The default is 1MiB.
+ docs: topics/running/ambassador/#modify-default-buffer-size
+
+ - title: Version number reported correctly
+ type: bugfix
+ body: >-
+ The release now shows its actual released version number, rather than
+ the internal development version number.
+ github:
+ - title: '#3854'
+ link: https://github.com/emissary-ingress/emissary/issues/3854
+ docs: https://github.com/emissary-ingress/emissary/issues/3854
+ image: ./v2.0.4-version.png
+
+ - title: Large configurations work correctly with Ambassador Cloud
+ type: bugfix
+ body: >-
+ Large configurations no longer cause $productName$ to be unable
+ to communicate with Ambassador Cloud.
+ github:
+ - title: '#3593'
+ link: https://github.com/emissary-ingress/emissary/issues/3593
+ docs: https://github.com/emissary-ingress/emissary/issues/3593
+
+ - title: Listeners correctly support l7Depth
+ type: bugfix
+ body: >-
+ The l7Depth element of the Listener CRD is
+ properly supported.
+ docs: topics/running/listener#l7depth
+ image: ./v2.0.4-l7depth.png
+
+ - version: 2.0.3-ea
+ date: '2021-09-16'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.3 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ isHeadline: true
+ docs: about/changes-2.x
+
+ - title: AES_LOG_LEVEL more widely effective
+ body: The environment variable AES_LOG_LEVEL now also sets the log level for the diagd logger.
+ type: feature
+ docs: topics/running/running/
+ github:
+ - title: '#3686'
+ link: https://github.com/emissary-ingress/emissary/issues/3686
+ - title: '#3666'
+ link: https://github.com/emissary-ingress/emissary/issues/3666
+
+ - title: AmbassadorMapping supports setting the DNS type
+ body: You can now set dns_type in the AmbassadorMapping to configure how Envoy will use the DNS for the service.
+ type: feature
+ docs: topics/using/mappings/#using-dns_type
+
+ - title: Building Emissary no longer requires setting DOCKER_BUILDKIT
+ body: It is no longer necessary to set DOCKER_BUILDKIT=0 when building Emissary. A future change will fully support BuildKit.
+ type: bugfix
+ docs: https://github.com/emissary-ingress/emissary/issues/3707
+ github:
+ - title: '#3707'
+ link: https://github.com/emissary-ingress/emissary/issues/3707
+
+ - version: 2.0.2-ea
+ date: '2021-08-24'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.2 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ isHeadline: true
+ docs: about/changes-2.x
+
+ - title: Envoy security updates
+ type: bugfix
+ body: 'Upgraded envoy to 1.17.4 to address security vulnerabilities CVE-2021-32777, CVE-2021-32778, CVE-2021-32779, and CVE-2021-32781.'
+ docs: https://groups.google.com/g/envoy-announce/c/5xBpsEZZDfE?pli=1
+
+ - title: Expose Envoy's allow_chunked_length HTTPProtocolOption
+ type: feature
+ body: 'You can now set allow_chunked_length in the Ambassador Module to configure the same value in Envoy.'
+ docs: topics/running/ambassador/#content-length-headers
+
+ - title: Envoy-configuration snapshots saved
+ type: change
+ body: Envoy-configuration snapshots get saved (as ambex-#.json) in /ambassador/snapshots. The number of snapshots is controlled by the AMBASSADOR_AMBEX_SNAPSHOT_COUNT environment variable; set it to 0 to disable. The default is 30.
+ docs: topics/running/running/
+
+ - version: 2.0.1-ea
+ date: '2021-08-12'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.1 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ isHeadline: true
+ docs: about/changes-2.x
+
+ - title: Improved Ambassador Cloud visibility
+ type: feature
+ body: Ambassador Agent reports sidecar process information and AmbassadorMapping OpenAPI documentation to Ambassador Cloud to provide more visibility into services and clusters.
+ docs: /docs/cloud/latest/service-catalog/quick-start/
+
+ - title: Configurable per-AmbassadorListener statistics prefix
+ body: The optional stats_prefix element of the AmbassadorListener CRD now determines the prefix of HTTP statistics emitted for a specific AmbassadorListener.
+ type: feature
+ docs: topics/running/listener
+
+ - title: Configurable statistics names
+ body: The optional stats_name element of AmbassadorMapping, AmbassadorTCPMapping, AuthService, LogService, RateLimitService, and TracingService now sets the name under which cluster statistics will be logged. The default is the service, with non-alphanumeric characters replaced by underscores.
+ type: feature
+ docs: topics/running/statistics
+
+ - title: Configurable Dev Portal fetch timeout
+ type: bugfix
+ body: The AmbassadorMapping resource can now specify docs.timeout_ms to set the timeout when the Dev Portal is fetching API specifications.
+ docs: topics/using/dev-portal/
+
+ - title: Dev Portal search strips HTML tags
+ type: bugfix
+ body: The Dev Portal will now strip HTML tags when displaying search results, showing just the actual content of the search result.
+ docs: topics/using/dev-portal/
+
+ - title: Updated klog to reduce log noise
+ type: bugfix
+ body: We have updated to k8s.io/klog/v2 to track upstream and to quiet unnecessary log output.
+ docs: https://github.com/emissary-ingress/emissary/issues/3603
+
+ - title: Subsecond time resolution in logs
+ type: change
+ body: Logs now include subsecond time resolutions, rather than just seconds.
+ docs: https://github.com/emissary-ingress/emissary/pull/3650
+
+ - title: Configurable Envoy-configuration rate limiting
+ type: change
+ body: Set AMBASSADOR_AMBEX_NO_RATELIMIT to true to completely disable ratelimiting Envoy reconfiguration under memory pressure. This can help performance with the endpoint or Consul resolvers, but could make OOMkills more likely with large configurations. The default is false, meaning that the rate limiter is active.
+ docs: topics/concepts/rate-limiting-at-the-edge/
+
+ - title: Improved Consul certificate rotation visibility
+ type: change
+ body: Consul certificate-rotation logging now includes the fingerprints and validity timestamps of certificates being rotated.
+ docs: howtos/consul/#consul-connector-and-encrypted-tls
+
+ - title: Add configurable cache for OIDC replies to the JWT Filter
+ type: feature
+ body: >-
+ The maxStale field is now supported in in the JWT Filter to configure how long $productname$ should cache OIDC responses for similar to the existing maxStale field in the OAuth2 Filter.
+ docs: topics/using/filters/jwt
+
+ - version: 2.0.0-ea
+ date: '2021-06-24'
+ notes:
+ - title: Developer Preview!
+ body: We're pleased to introduce $productName$ 2.0.0 as a developer preview. The 2.X family introduces a number of changes to allow $productName$ to more gracefully handle larger installations, reduce global configuration to better handle multitenant or multiorganizational installations, reduce memory footprint, and improve performance. We welcome feedback!! Join us on Slack and let us know what you think.
+ type: change
+ docs: about/changes-2.x
+ isHeadline: true
+
+ - title: Configuration API v3alpha1
+ body: >-
+ $productName$ 2.0.0 introduces API version x.getambassador.io/v3alpha1 for
+ configuration changes that are not backwards compatible with the 1.X family. API versions
+ getambassador.io/v0, getambassador.io/v1, and
+ getambassador.io/v2 are deprecated. Further details are available in the Major Changes
+ in 2.X document.
+ type: feature
+ docs: about/changes-2.x/#1-configuration-api-version-getambassadoriov3alpha1
+ image: ./edge-stack-2.0.0-v3alpha1.png
+
+ - title: The AmbassadorListener Resource
+ body: The new AmbassadorListener CRD defines where and how to listen for requests from the network, and which AmbassadorHost definitions should be used to process those requests. Note that the AmbassadorListener CRD is mandatory and consolidates all port configuration; see the AmbassadorListener documentation for more details.
+ type: feature
+ docs: topics/running/listener
+ image: ./edge-stack-2.0.0-listener.png
+
+ - title: AmbassadorMapping hostname DNS glob support
+ body: >-
+ Where AmbassadorMapping's host field is either an exact match or (with host_regex set) a regex,
+ the new hostname element is always a DNS glob. Use hostname instead of host for best results.
+ docs: about/changes-2.x/#ambassadorhost-and-ambassadormapping-association
+ type: feature
+
+ - title: Memory usage improvements for installations with many AmbassadorHosts
+ body: The behavior of the Ambassador module prune_unreachable_routes field is now automatic, which should reduce Envoy memory requirements for installations with many AmbassadorHosts
+ docs: topics/running/ambassador/#prune-unreachable-routes
+ image: ./edge-stack-2.0.0-prune_routes.png
+ type: feature
+
+ - title: Independent Host actions supported
+ body: Each AmbassadorHost can specify its requestPolicy.insecure.action independently of any other AmbassadorHost, allowing for HTTP routing as flexible as HTTPS routing.
+ docs: topics/running/host-crd/#secure-and-insecure-requests
+ github:
+ - title: '#2888'
+ link: https://github.com/datawire/ambassador/issues/2888
+ image: ./edge-stack-2.0.0-insecure_action_hosts.png
+ type: bugfix
+
+ - title: Correctly set Ingress resource status in all cases
+ body: $productName$ 2.0.0 fixes a regression in detecting the Ambassador Kubernetes service that could cause the wrong IP or hostname to be used in Ingress statuses -- thanks, Noah Fontes!
+ docs: topics/running/ingress-controller
+ type: bugfix
+ image: ./edge-stack-2.0.0-ingressstatus.png
+
+ - title: Stricter mTLS enforcement
+ body: $productName$ 2.0.0 fixes a bug where mTLS could use the wrong configuration when SNI and the :authority header didn't match
+ type: bugfix
+
+ - title: Port configuration outside AmbassadorListener has been moved to AmbassadorListener
+ body: The TLSContextredirect_cleartext_from and AmbassadorHostrequestPolicy.insecure.additionalPort elements are no longer supported. Use a AmbassadorListener for this functionality instead.
+ type: change
+ docs: about/changes-2.x/#tlscontext-redirect_cleartext_from-and-host-insecureadditionalport
+
+ - title: PROXY protocol configuration has been moved to AmbassadorListener
+ body: The use_proxy_protocol element of the Ambassador Module is no longer supported, as it is now part of the AmbassadorListener resource (and can be set per-AmbassadorListener rather than globally).
+ type: change
+ docs: about/changes-2.x/#proxy-protocol-configuration
+
+ - title: Stricter rules for AmbassadorHost/AmbassadorMapping association
+ body: An AmbassadorMapping will only be matched with an AmbassadorHost if the AmbassadorMapping's host or the AmbassadorHost's selector (or both) are explicitly set, and match. This change can significantly improve $productName$'s memory footprint when many AmbassadorHosts are involved. Further details are available in the 2.0.0 Changes document.
+ docs: about/changes-2.x/#host-and-mapping-association
+ type: change
+
+ - title: AmbassadorHost or Ingress now required for TLS termination
+ body: An AmbassadorHost or Ingress resource is now required when terminating TLS -- simply creating a TLSContext is not sufficient. Further details are available in the AmbassadorHost CRD documentation.
+ docs: about/changes-2.x/#host-tlscontext-and-tls-termination
+ type: change
+ image: ./edge-stack-2.0.0-host_crd.png
+
+ - title: Envoy V3 APIs
+ body: By default, $productName$ will configure Envoy using the V3 Envoy API. This change is mostly transparent to users, but note that Envoy V3 does not support unsafe regular expressions or, e.g., Zipkin's V1 collector protocol. Further details are available in the Major Changes in 2.X document.
+ type: change
+ docs: about/changes-2.x/#envoy-v3-api-by-default
+
+ - title: Module-based TLS no longer supported
+ body: The tls module and the tls field in the Ambassador module are no longer supported. Please use TLSContext resources instead.
+ docs: about/changes-2.x/#tls-the-ambassador-module-and-the-tls-module
+ image: ./edge-stack-2.0.0-tlscontext.png
+ type: change
+
+ - title: Higher performance while generating Envoy configuration now enabled by default
+ body: The environment variable AMBASSADOR_FAST_RECONFIGURE is now set by default, enabling the higher-performance implementation of the code that $productName$ uses to generate and validate Envoy configurations.
+ docs: topics/running/scaling/#ambassador_fast_reconfigure-and-ambassador_legacy_mode-flags
+ type: change
+
+ - title: Service Preview no longer supported
+ body: >-
+ Service Preview and the AGENT_SERVICE environment variable are no longer supported.
+ The Telepresence product replaces this functionality.
+ docs: https://www.getambassador.io/docs/telepresence/
+ type: change
+
+ - title: edgectl no longer supported
+ body: The edgectl CLI tool has been deprecated; please use the emissary-ingress helm chart instead.
+ docs: topics/install/helm/
+ type: change
+
+ - version: 1.14.2
+ date: '2021-09-29'
+ notes:
+ - title: Mappings support controlling DNS refresh with DNS TTL
+ type: feature
+ body: >-
+ You can now set respect_dns_ttl in Ambassador Mappings. When true it
+ configures that upstream's refresh rate to be set to resource record’s TTL
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+
+ - title: Mappings support configuring strict or logical DNS
+ type: feature
+ body: >-
+ You can now set dns_type in Ambassador Mappings to use Envoy's
+ logical_dns resolution instead of the default strict_dns.
+ docs: topics/using/mappings/#dns-configuration-for-mappings
+
+ - title: Support configuring upstream buffer size
+ type: feature
+ body: >-
+ You can now set buffer_limit_bytes in the ambassador
+ Module to to change the size of the upstream read and write buffers.
+ The default is 1MiB.
+ docs: topics/running/ambassador/#modify-default-buffer-size
+
+ - version: 1.14.1
+ date: '2021-08-24'
+ notes:
+ - title: Envoy security updates
+ type: change
+ body: >-
+ Upgraded Envoy to 1.17.4 to address security vulnerabilities CVE-2021-32777,
+ CVE-2021-32778, CVE-2021-32779, and CVE-2021-32781.
+ docs: https://groups.google.com/g/envoy-announce/c/5xBpsEZZDfE
+
+ - version: 1.14.0
+ date: '2021-08-19'
+ notes:
+ - title: Envoy upgraded to 1.17.3!
+ type: change
+ body: >-
+ Update from Envoy 1.15 to 1.17.3
+ docs: https://www.envoyproxy.io/docs/envoy/latest/version_history/version_history
+
+ - title: Expose Envoy's allow_chunked_length HTTPProtocolOption
+ type: feature
+ body: >-
+ You can now set allow_chunked_length in the Ambassador Module to configure
+ the same value in Envoy.
+ docs: topics/running/ambassador/#content-length-headers
+
+ - title: Default Envoy API version is now V3
+ type: change
+ body: >-
+ AMBASSADOR_ENVOY_API_VERSION now defaults to V3
+ docs: topics/running/running/#ambassador_envoy_api_version
+
+ - title: Subsecond time resolution in logs
+ type: change
+ body: Logs now include subsecond time resolutions, rather than just seconds.
+ docs: https://github.com/emissary-ingress/emissary/pull/3650
+
+ - version: 1.13.10
+ date: '2021-07-28'
+ notes:
+ - title: Fix for CORS origins configuration on the Mapping resource
+ type: bugfix
+ body: >-
+ Fixed a regression when specifying a comma separated string for cors.origins
+ on the Mapping resource.
+ ([#3609](https://github.com/emissary-ingress/emissary/issues/3609))
+ docs: topics/using/cors
+ image: ../images/emissary-1.13.10-cors-origin.png
+
+ - title: New Envoy-configuration snapshots for debugging
+ body: 'Envoy-configuration snapshots get saved (as ambex-#.json) in /ambassador/snapshots. The number of snapshots is controlled by the AMBASSADOR_AMBEX_SNAPSHOT_COUNT environment variable; set it to 0 to disable. The default is 30.'
+ type: change
+ docs: topics/running/environment/
+
+ - title: Optionally remove ratelimiting for Envoy reconfiguration
+ body: >-
+ Set AMBASSADOR_AMBEX_NO_RATELIMIT to true to completely disable
+ ratelimiting Envoy reconfiguration under memory pressure. This can help performance with
+ the endpoint or Consul resolvers, but could make OOMkills more likely with large
+ configurations. The default is false, meaning that the rate limiter is
+ active.
+ type: change
+ docs: topics/running/environment/
+
+ - title: Mappings support configuring the DevPortal fetch timeout
+ type: bugfix
+ body: >-
+ The Mapping resource can now specify docs.timeout_ms to set the
+ timeout when the Dev Portal is fetching API specifications.
+ docs: topics/using/dev-portal
+ image: ../images/edge-stack-1.13.10-docs-timeout.png
+
+ - title: Dev Portal will strip HTML tags when displaying results
+ type: bugfix
+ body: >-
+ The Dev Portal will now strip HTML tags when displaying search results, showing just the
+ actual content of the search result.
+ docs: topics/using/dev-portal
+
+ - title: Consul certificate rotation logs more information
+ type: change
+ body: >-
+ Consul certificate-rotation logging now includes the fingerprints and validity timestamps
+ of certificates being rotated.
+ docs: howtos/consul/
+ image: ../images/edge-stack-1.13.10-consul-cert-log.png
+
+ - version: 1.13.9
+ date: '2021-06-30'
+ notes:
+ - title: Fix for TCPMappings
+ body: >-
+ Configuring multiple TCPMappings with the same ports (but different hosts) no longer
+ generates invalid Envoy configuration.
+ type: bugfix
+ docs: topics/using/tcpmappings/
+
+ - version: 1.13.8
+ date: '2021-06-08'
+ notes:
+ - title: Fix Ambassador Cloud Service Details
+ body: >-
+ Ambassador Agent now accurately reports up-to-date Endpoint information to Ambassador
+ Cloud
+ type: bugfix
+ docs: tutorials/getting-started/#3-connect-your-cluster-to-ambassador-cloud
+ image: ../images/edge-stack-1.13.8-cloud-bugfix.png
+
+ - title: Improved Argo Rollouts Experience with Ambassador Cloud
+ body: >-
+ Ambassador Agent reports ConfigMaps and Deployments to Ambassador Cloud to provide a
+ better Argo Rollouts experience. See [Argo+Ambassador
+ documentation](https://www.getambassador.io/docs/argo) for more info.
+ type: feature
+ docs: https://www.getambassador.io/docs/argo
+
+ - version: 1.13.7
+ date: '2021-06-03'
+ notes:
+ - title: JSON logging support
+ body: >-
+ Add AMBASSADOR_JSON_LOGGING to enable JSON for most of the Ambassador control plane. Some
+ (but few) logs from gunicorn and the Kubernetes client-go package still log text.
+ image: ../images/edge-stack-1.13.7-json-logging.png
+ docs: topics/running/running/#log-format
+ type: feature
+
+ - title: Consul resolver bugfix with TCPMappings
+ body: >-
+ Fixed a bug where the Consul resolver would not actually use Consul endpoints with
+ TCPMappings.
+ image: ../images/edge-stack-1.13.7-tcpmapping-consul.png
+ docs: topics/running/resolvers/#the-consul-resolver
+ type: bugfix
+
+ - title: Memory usage calculation improvements
+ body: >-
+ Ambassador now calculates its own memory usage in a way that is more similar to how the
+ kernel OOMKiller tracks memory.
+ image: ../images/edge-stack-1.13.7-memory.png
+ docs: topics/running/scaling/#inspecting-ambassador-performance
+ type: change
+
+ - version: 1.13.6
+ date: '2021-05-24'
+ notes:
+ - title: Quieter logs in legacy mode
+ type: bugfix
+ body: >-
+ Fixed a regression where Ambassador snapshot data was logged at the INFO label
+ when using AMBASSADOR_LEGACY_MODE=true.
+
+ - version: 1.13.5
+ date: '2021-05-13'
+ notes:
+ - title: Correctly support proper_case and preserve_external_request_id
+ type: bugfix
+ body: >-
+ Fix a regression from 1.8.0 that prevented ambassadorModule
+ config keys proper_case and preserve_external_request_id
+ from working correctly.
+ docs: topics/running/ambassador/#header-case
+
+ - title: Correctly support Ingress statuses in all cases
+ type: bugfix
+ body: >-
+ Fixed a regression in detecting the Ambassador Kubernetes service that could cause the
+ wrong IP or hostname to be used in Ingress statuses (thanks, [Noah
+ Fontes](https://github.com/impl)!
+ docs: topics/running/ingress-controller
+
+ - version: 1.13.4
+ date: '2021-05-11'
+ notes:
+ - title: Envoy 1.15.5
+ body: >-
+ Incorporate the Envoy 1.15.5 security update by adding the
+ reject_requests_with_escaped_slashes option to the Ambassador module.
+ image: ../images/edge-stack-1.13.4.png
+ docs: topics/running/ambassador/#rejecting-client-requests-with-escaped-slashes
+ type: security
+# Don't go any further back than 1.13.4.
diff --git a/docs/edge-stack/latest/topics/install/helm.md b/docs/edge-stack/latest/topics/install/helm.md
new file mode 100644
index 000000000..84c40d586
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/helm.md
@@ -0,0 +1,107 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Install with Helm
+
+
+
+ To migrate from $productName$ 1.X to $productName$ 2.X, see the
+ [$productName$ migration matrix](../migration-matrix/). This guide
+ **will not work** for that, due to changes to the configuration
+ resources used for $productName$ 2.X.
+
+
+
+[Helm](https://helm.sh) is a package manager for Kubernetes that automates the release and management of software on Kubernetes. $productName$ can be installed via a Helm chart with a few simple steps, depending on if you are deploying for the first time, upgrading $productName$ from an existing installation, or migrating from $productName$.
+
+## Before you begin
+
+
+ $productName$ requires a valid license or cloud connect token to start. You can refer to the quickstart guide
+ for instructions on how to obtain a free community license and connect your installation to Ambassador cloud.
+
+
+The $productName$ Helm chart is hosted by Datawire and published at `https://app.getambassador.io`.
+
+Start by adding this repo to your helm client with the following command:
+
+```bash
+helm repo add datawire https://app.getambassador.io
+helm repo update
+```
+
+## Install with Helm
+
+When you run the Helm chart, it installs $productName$.
+
+1. Install the $productName$ CRDs.
+
+ Before installing $productName$ $version$ itself, you must configure your
+ Kubernetes cluster to support the `getambassador.io/v3alpha1` and `getambassador.io/v2`
+ configuration resources. This is required.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. Install the $productName$ Chart with the following command:
+
+ ```
+ helm install -n $productNamespace$ --create-namespace \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+3. Next Steps
+
+ $productName$ should now be successfully installed and running, but in order to get started deploying Services and test routing to them you need to configure a few more resources.
+
+ - [The `Listener` Resource](../../running/listener/) is required to configure which ports the $productName$ pods listen on so that they can begin responding to requests.
+ - [The `Mapping` Resouce](../../using/intro-mappings/) is used to configure routing requests to services in your cluster.
+ - [The `Host` Resource](../../running/host-crd/) configures TLS termination for enablin HTTPS communication.
+ - Explore how $productName$ [configures communication with clients](../../../howtos/configure-communications)
+
+
+ We strongly recommend following along with our Quickstart Guide to get started by creating a Listener, deploying a simple service to test with, and setting up a Mapping to route requests from $productName$ to the demo service.
+
+
+
+ $productName$ $version$ includes a Deployment in the $productNamespace$ namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+For more advanced configuration and details about helm values,
+[please see the helm chart.](https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$)
+
+## Upgrading an existing installation
+
+See the [migration matrix](../migration-matrix) for instructions about upgrading
+$productName$.
diff --git a/docs/edge-stack/latest/topics/install/index.less b/docs/edge-stack/latest/topics/install/index.less
new file mode 100644
index 000000000..bc649e7ca
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/index.less
@@ -0,0 +1,57 @@
+@media (max-width: 769px) {
+ #index-installContainer {
+ flex-direction: column;
+ }
+ .index-dropdown {
+ width: auto;
+ }
+ .index-dropBtn {
+ width: 100%;
+ }
+}
+
+.index-dropBtn {
+ background-color: #8e77ff;
+ color: white;
+ padding: 10px;
+ font-size: 16px;
+ border: none;
+ margin-top: -20px;
+}
+
+.index-dropdown {
+ position: relative;
+ display: inline-block;
+}
+
+.index-dropdownContent {
+ display: none;
+ position: absolute;
+ background-color: #f1f1f1;
+ width: 100%;
+ box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+ z-index: 1;
+}
+
+.index-dropdownContent a {
+ color: black;
+ padding: 12px 16px;
+ text-decoration: none;
+ display: block;
+}
+
+.index-dropdownContent a:hover {
+ background-color: #ddd;
+}
+
+.index-dropdown:hover .index-dropdownContent {
+ display: block;
+}
+
+.index-dropdown:hover .index-dropBtn {
+ background-color: #5f3eff;
+}
+
+#index-installContainer {
+ display: flex;
+}
diff --git a/docs/edge-stack/latest/topics/install/index.md b/docs/edge-stack/latest/topics/install/index.md
new file mode 100644
index 000000000..ac6a79d41
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/index.md
@@ -0,0 +1,40 @@
+import Alert from '@material-ui/lab/Alert';
+import './index.less'
+
+# Installing $productName$
+
+## Install with Helm
+Helm, the package manager for Kubernetes, is the recommended way to install
+$productName$. Full details are in the [Helm instructions.](helm/)
+
+## Install with Kubernetes YAML
+Another way to install $productName$ if you are unable to use Helm is to
+directly apply Kubernetes YAML. See details in the
+[manual YAML installation instructions.](yaml-install).
+
+## Try the demo with Docker
+The Docker install will let you try the $productName$ locally in seconds,
+but is not supported for production workloads. [Try $productName$ on Docker.](docker/)
+
+## Upgrade or migrate to a newer version
+If you already have an existing installation of $AESproductName$ or
+$OSSproductName$, you can upgrade your instance. The [migration matrix](migration-matrix/)
+shows you how.
+
+## Container Images
+Although our installation guides will favor using the `docker.io` container registry,
+we publish $AESproductName$ and $OSSproductName$ releases to multiple registries.
+
+Starting with version 1.0.0, you can pull the aes image from any of the following registries:
+- `docker.io/datawire/`
+- `gcr.io/datawire/`
+
+We want to give you flexibility and independence from a hosting platform's uptime to support
+your production needs for $AESproductName$ or $OSSproductName$. Read more about
+[Running $productName$ in Production](../running).
+
+# What’s Next?
+$productName$ has a comprehensive range of [features](/features/) to
+support the requirements of any edge microservice. To learn more about how $productName$ works, along with use cases, best practices, and more,
+check out the [Welcome page](../../tutorials/getting-started/) or read the [$productName$
+Story](../../about/why-ambassador).
diff --git a/docs/edge-stack/latest/topics/install/migrate-to-3-alternate.md b/docs/edge-stack/latest/topics/install/migrate-to-3-alternate.md
new file mode 100644
index 000000000..d0b791a12
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/migrate-to-3-alternate.md
@@ -0,0 +1,36 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrading $productName$ with a separate cluster
+
+You can upgrade from any version of $AESproductName$ or $OSSproductName$ to
+any version of either by installing the new version in a new Kubernetes cluster,
+then copying over configuration as needed. This is the way to be absolutely
+certain that each installation cannot affect the other: it is extremely safe,
+but is also significantly more effort.
+
+For example, to upgrade from some other version of $AESproductName$ or
+$OSSproductName$ to $productName$ $version$:
+
+1. Install $productName$ $version$ in a completely new cluster.
+
+2. **Create `Listener`s for $productName$ $version$.**
+
+ When $productName$ $version$ starts, it will not have any `Listener`s, and it will not
+ create any. You must create `Listener` resources by hand, or $productName$ $version$
+ will not listen on any ports.
+
+3. Copy the entire configuration from the $productName$ 1.X cluster to the $productName$
+ $version$ cluster. This is most simply done with `kubectl get -o yaml | kubectl apply -f -`.
+
+ This will create `getambassador.io/v2` resources in the $productName$ $version$ cluster.
+ $productName$ $version$ will translate them internally to `getambassador.io/v3alpha1`
+ resources.
+
+4. Each $productName$ instance has its own cluster, so you can test the new
+ instance without disrupting traffic to the existing instance.
+
+5. If you need to make changes, you can change the `getambassador.io/v2` resource, or convert the
+ resource you're changing to `getambassador.io/v3alpha1` by using `kubectl edit`.
+
+6. Once everything is working with both versions, transfer incoming traffic to the $productName$
+ $version$ cluster.
diff --git a/docs/edge-stack/latest/topics/install/migration-matrix.md b/docs/edge-stack/latest/topics/install/migration-matrix.md
new file mode 100644
index 000000000..ead43c6e0
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/migration-matrix.md
@@ -0,0 +1,48 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrading $productName$
+
+
+ Read the instructions below before making any changes to your cluster!
+
+
+There are currently multiple paths for upgrading $productName$, depending on what version you're currently
+running, what you want to be running, and whether you installed $productName$ using [Helm](../helm) or
+YAML.
+
+(To check out if you installed $productName$ using Helm, run `helm list --all` and see if
+$productName$ is listed. If so, you installed using Helm.)
+
+
+ Read the instructions below before making any changes to your cluster!
+
+
+## If you are currently running $OSSproductName$
+
+See the [instructions on updating $OSSproductName$](../../../../../emissary/$ossDocsVersion$/topics/install/migration-matrix).
+
+## If you installed $productName$ using Helm
+
+| If you're running. | You can upgrade to |
+|-----------------------------------------|----------------------------------------------------------------------------------|
+| $AESproductName$ 3.8.X | [$AESproductName$ $version$](../upgrade/helm/edge-stack-3.8/edge-stack-3.X) |
+| $AESproductName$ 3.7.X | [$AESproductName$ $version$](../upgrade/helm/edge-stack-3.7/edge-stack-3.X) |
+| $AESproductName$ $versionTwoX$ | [$AESproductName$ $version$](../upgrade/helm/edge-stack-2.5/edge-stack-3.X) |
+| $AESproductName$ 2.4.X | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-2.4/edge-stack-2.X) |
+| $AESproductName$ 2.0.X | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-2.0/edge-stack-2.X) |
+| $AESproductName$ $versionOneX$ | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-1.14/edge-stack-2.X) |
+| $AESproductName$ prior to $versionOneX$ | [$AESproductName$ $versionOneX$](../../../../1.14/topics/install/upgrading) |
+| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/helm/emissary-3.9/edge-stack-3.X) |
+
+## If you installed $AESproductName$ manually by applying YAML
+
+| If you're running. | You can upgrade to |
+|-----------------------------------------|----------------------------------------------------------------------------------|
+| $AESproductName$ 3.8.X | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-3.8/edge-stack-3.X) |
+| $AESproductName$ 3.7.X | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-3.7/edge-stack-3.X) |
+| $AESproductName$ $versionTwoX$ | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-2.5/edge-stack-3.X) |
+| $AESproductName$ 2.4.X | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-2.4/edge-stack-2.X) |
+| $AESproductName$ 2.0.X | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-2.0/edge-stack-2.X) |
+| $AESproductName$ $versionOneX$ | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-1.14/edge-stack-2.X) |
+| $AESproductName$ prior to $versionOneX$ | [$AESproductName$ $versionOneX$](../../../../1.14/topics/install/upgrading) |
+| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/yaml/emissary-3.9/edge-stack-3.X) |
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-1.14/edge-stack-2.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-1.14/edge-stack-2.X.md
new file mode 100644
index 000000000..88983d794
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-1.14/edge-stack-2.X.md
@@ -0,0 +1,378 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 1.14.X to $productName$ $versionTwoX$ (Helm)
+
+
+ This guide covers migrating from $productName$ 1.14.X to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation originally made using Helm.
+ If you did not install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+We're pleased to introduce $productName$ $versionTwoX$! The 2.X family introduces a number of
+changes to allow $productName$ to more gracefully handle larger installations (including
+multitenant or multiorganizational installations), reduce memory footprint, and improve
+performance. In keeping with [SemVer](https://semver.org), $productName$ 2.X introduces
+some changes that aren't backward-compatible with 1.X. These changes are detailed in
+[Major Changes in $productName$ 2.X](../../../../../../about/changes-2.x).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $productName$ 1.14 and $productName$
+$versionTwoX$ side-by-side in the same cluster. This gives $productName$ $versionTwoX$
+and $productName$ 1.14 access to all the same configuration resources, with some
+important caveats:
+
+1. **$productName$ 1.14 will not see any `getambassador.io/v3alpha1` resources.**
+
+ This is intentional; it provides a way to apply configuration only to
+ $productName$ $versionTwoX$, while not interfering with the operation of your
+ $productName$ 1.14 installation.
+
+2. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $productName$ $versionTwoX$ installation from
+ seeing a particular bit of $productName$ 1.14 configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $productName$ $versionTwoX$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $productName$ $versionTwoX$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+3. **$productName$ 1.14 must remain in control of ACME while both installations are running.**
+
+ The processes that handle ACME challenges cannot be managed by both $productName$
+ 1.X and $productName$ $versionTwoX$ at the same time. The instructions below disable ACME
+ in $productName$ $versionTwoX$, allowing $productName$ 1.14 to continue managing it.
+
+ This implies that any new `Host`s used for $productName$ 1.14 should be created using
+ `getambassador.io/v2` so that $productName$ 1.14 can see them.
+
+4. **Check `AuthService` and `RateLimitService` resources, if any.**
+
+ If you have an [`AuthService`](../../../../../using/authservice/) or
+ [`RateLimitService`](../../../../../running/services/rate-limit-service) installed, make
+ sure that they are using the [namespace-qualified DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services).
+ If they are not, the initial migration tests may fail.
+
+ Additionally, when installing with Helm, you must make sure that $productName$ $versionTwoX$
+ does not attempt to create duplicate `AuthService` and `RateLimitService` entries. Add
+
+ ```
+ --set rateLimit.create=false
+ ```
+
+ and
+
+ ```
+ --set authService.create=false
+ ```
+
+ on the Helm command line to prevent duplicating these resources.
+
+5. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The best way to avoid multiple agents when installing with Helm is to use
+ `--set emissary-ingress.agent.enabled=false` to tell Helm not to install a
+ new Agent with $productName$ $versionTwoX$. Once testing is done, you can switch
+ Agents safely.
+
+6. **If you use ACME for multiple `Host`s, add a wildcard `Host` too.**
+
+ This is required to manage a known issue. This issue will be resolved in a future
+ $AESproductName$ release.
+
+7. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $productName$ 1.14 that use selectors that will match
+ Pods from $productName$ $versionTwoX$, traffic will be erroneously split between
+ $productName$ 1.14 and $productName$ $versionTwoX$. The labels used by $productName$
+ $versionTwoX$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $productName$ $versionTwoX$ in a separate cluster](../../../../migrate-to-2-alternate).
+This permits absolute certainty that your $productName$ 1.14 configuration will not be
+affected by changes meant for $productName$ $versionTwoX$, and it eliminates concerns about
+ACME, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is an eight-step process:
+
+1. **Make sure that older configuration resources are not present.**
+
+ $productName$ 2.X does not support `getambassador.io/v0` or `getambassador.io/v1`
+ resources, and Kubernetes will not permit removing support for CRD versions that are
+ still in use for stored resources. To verify that no resources older than
+ `getambassador.io/v2` are active, run
+
+ ```
+ kubectl get crds -o 'go-template={{range .items}}{{.metadata.name}}={{.status.storedVersions}}{{"\n"}}{{end}}' | fgrep getambassador.io
+ ```
+
+ If `v1` is present in the output, **do not begin migration.** The old resources must be
+ converted to `getambassador.io/v2` and the `storedVersion` information in the cluster
+ must be updated. If necessary, contact Ambassador Labs on [Slack](http://a8r.io/slack)
+ for more information.
+
+2. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you must configure your
+ Kubernetes cluster to support its new `getambassador.io/v3alpha1` configuration
+ resources. Note that `getambassador.io/v2` resources are still supported, but **you
+ must install support for `getambassador.io/v3alpha1`** to run $productName$ $versionTwoX$,
+ even if you intend to continue using only `getambassador.io/v2` resources for some
+ time.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, you need to install $productName$ $versionTwoX$ itself
+ **in the same namespace as your existing $productName$ 1.14 installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+
+ Make sure that you set the AES_ACME_LEADER_DISABLE flag. This prevents
+ $productName$ $versionTwoX$ from trying to manage ACME, so that $productName$ 1.14 can
+ do it instead.
+
+
+ Start by making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Typically, $productName$ 1.14 was installed in the `ambassador` namespace. If you installed
+ $productName$ 1.14 in a different namespace, change the namespace in the commands below.
+
+ - If you do not need to set `AMBASSADOR_LABEL_SELECTOR`:
+
+ ```bash
+ helm install -n ambassador \
+ --set emissary-ingress.agent.enabled=false \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE=true \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ```
+
+ - If you do need to set `AMBASSADOR_LABEL_SELECTOR`, use `--set`, for example:
+
+ ```bash
+ helm install -n ambassador \
+ --set emissary-ingress.agent.enabled=false \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE=true \
+ --set emissary-ingress.env.AMBASSADOR_LABEL_SELECTOR="version-two=true" \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart for $productName$ $versionTwoX$.
+ Do not use the ambassador Helm chart.
+
+
+4. **Install `Listener`s and `Host`s as needed.**
+
+ An important difference between $productName$ 1.14 and $productName$ $versionTwoX$ is the
+ new **mandatory** `Listener` CRD. Also, when running both installations side by side,
+ you will need to make sure that a `Host` is present for the new $productName$ $versionTwoX$
+ Service. For example:
+
+ ```bash
+ kubectl apply -f - <
+ Make sure that any Hosts you create use API version getambassador.io/v2,
+ so that they can be managed by $productName$ 1.14 as long as both installations are running.
+
+
+ This example requires that you know the hostname for the $productName$ Service (`$EMISSARY_HOSTNAME`)
+ and that you have created a TLS Secret for it in `$EMISSARY_TLS_SECRET`.
+
+5. **Test!**
+
+ Your $productName$ $versionTwoX$ installation can support the `getambassador.io/v2`
+ configuration resources used by $productName$ 1.14, but you may need to make some
+ changes to the configuration, as detailed in the documentation on
+ [configuring $productName$ Communications](../../../../../../howtos/configure-communications)
+ and [updating CRDs to `getambassador.io/v3alpha1`](../../../../convert-to-v3alpha1).
+
+
+ Kubernetes will not allow you to have a getambassador.io/v3alpha1 resource
+ with the same name as a getambassador.io/v2 resource or vice versa: only
+ one version can be stored at a time.
+
+ If you find that your $productName$ $versionTwoX$ installation and your $productName$ 1.14
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 2, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your 1.14 CRDs, delete your
+ installation of $productName$ $versionTwoX$, and delete the `emissary-system` namespace.
+
+6. **When ready, switch over to $productName$ $versionTwoX$.**
+
+ You can run $productName$ 1.14 and $productName$ $versionTwoX$ side-by-side as long as you care
+ to. However, taking full advantage of $productName$ 2.X's capabilities **requires**
+ [updating your configuration to use `getambassador.io/v3alpha1` configuration resources](../../../../convert-to-v3alpha1),
+ since some useful features in $productName$ $versionTwoX$ are only available using
+ `getambassador.io/v3alpha1` resources.
+
+ When you're ready to have $productName$ $versionTwoX$ handle traffic on its own, switch
+ your original $productName$ 1.14 Service to point to $productName$ $versionTwoX$. Use
+ `kubectl edit service ambassador` and change the `selectors` to:
+
+ ```
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+7. **Install the $productName$ $versionTwoX$ Ambassador Agent.**
+
+ First, scale the 1.14 agent to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent. **Note that if you needed to set
+ `AMBASSADOR_LABEL_SELECTOR`, you must add that to this `helm upgrade` command.**
+
+ ```bash
+ helm upgrade -n ambassador \
+ --set emissary-ingress.agent.enabled=true \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE=true \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ```
+
+8. **Finally, enable ACME in $productName$ $versionTwoX$.**
+
+ First, scale the 1.14 Ambassador to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador --replicas=0
+ ```
+
+ Once that's done, enable ACME in $productName$ $versionTwoX$. **Note that if you
+ needed to set `AMBASSADOR_LABEL_SELECTOR`, you must add that to this
+ `helm upgrade` command.**
+
+ ```bash
+ helm upgrade -n ambassador \
+ --set emissary-ingress.agent.enabled=true \
+ --set rateLimit.create=false \
+ --set authService.create=false \
+ --set emissary-ingress.env.AES_ACME_LEADER_DISABLE= \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n ambassador deployment/edge-stack -w
+ ````
+
+Congratulations! At this point, $productName$ $versionTwoX$ is fully running, and
+it's safe to remove the old `ambassador` and `ambassador-agent` Deployments:
+
+```
+kubectl delete -n ambassador deployment/ambassador deployment/ambassador-agent
+```
+
+Once $productName$ 1.14 is no longer running, you may [convert](../../../../convert-to-v3alpha1)
+any remaining `getambassador.io/v2` resources to `getambassador.io/v3alpha1`.
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.0/edge-stack-2.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.0/edge-stack-2.X.md
new file mode 100644
index 000000000..c9d337839
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.0/edge-stack-2.X.md
@@ -0,0 +1,92 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.0.5 to $productName$ $versionTwoX$ (Helm)
+
+
+ This guide covers migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation originally made using Helm.
+ If you did not install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Upgrading from $productName$ 2.0.5 to $productName$ $versionTwoX$ typically requires downtime.
+ In some situations, Ambassador Labs Support may be able to assist with a zero-downtime migration;
+ contact support with questions.
+
+
+Migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$ is a four-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Delete $productName$ 2.0.5 Deployment.**
+
+
+ Delete only the Deployment for $productName$ 2.0.5 in order to preserve all of
+ your existing configuration.
+
+
+ Use `kubectl` to delete the Deployment for $productName$ 2.0.5. Typically, this will be found
+ in the `ambassador` namespace.
+
+ ```
+ kubectl delete -n ambassador deployment edge-stack
+ ```
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $versionTwoX$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, install $productName$ in the `$productNamespace$` namespace. If necessary for
+ your installation (e.g. if you were running with `AMBASSADOR_SINGLE_NAMESPACE` set),
+ you can choose a different namespace.
+
+ ```bash
+ helm install -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 2.X.
+ Do not use the ambassador Helm chart.
+
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.4/edge-stack-2.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.4/edge-stack-2.X.md
new file mode 100644
index 000000000..f11054800
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.4/edge-stack-2.X.md
@@ -0,0 +1,75 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.4.X to $productName$ $versionTwoX$ (Helm)
+
+
+ This guide covers migrating from $productName$ 2.4 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between minor
+versions is straightforward.
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $versionTwoX$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 2.X.
+ Do not use the ambassador Helm chart.
+
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md
new file mode 100644
index 000000000..e0d02c0ec
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md
@@ -0,0 +1,154 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.5.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 2.5.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md
new file mode 100644
index 000000000..c988b1123
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md
@@ -0,0 +1,152 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.4.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 3.4.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x to 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md
new file mode 100644
index 000000000..ab9bbf890
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md
@@ -0,0 +1,152 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.7.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 3.7.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x to 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md
new file mode 100644
index 000000000..141473272
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md
@@ -0,0 +1,152 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.8.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 3.8.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x to 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/latest/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md
new file mode 100644
index 000000000..e6dbff12b
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md
@@ -0,0 +1,241 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $OSSproductName$ $version$ to $AESproductName$ $version$ (Helm)
+
+
+ This guide covers migrating from $OSSproductName$ $version$ to $AESproductName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation originally made using Helm.
+ If you did not install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+You can upgrade from $OSSproductName$ to $AESproductName$ with a few simple commands. When you upgrade to $AESproductName$, you'll be able to access additional capabilities such as **automatic HTTPS/TLS termination, Swagger/OpenAPI support, API catalog, Single Sign-On, and more.** For more about the differences between $AESproductName$ and $OSSproductName$, see the [Editions page](/editions).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $OSSproductName$ $version$ and $AESproductName$
+$version$ side-by-side in the same cluster. This gives $AESproductName$ $version$
+and $AESproductName$ $version$ access to all the same configuration resources, with some
+important notes:
+
+1. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $AESproductName$ $version$ installation from
+ seeing a particular bit of $OSSproductName$ $version$ configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $AESproductName$ $version$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $AESproductName$ $version$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+2. **$AESproductName$ ACME and `Filter`s will be disabled while $OSSproductName$ is still running.**
+
+ Since $AESproductName$ and $OSSproductName$ share configuration, $AESproductName$ cannot
+ configure its ACME or other filter processors without also affecting $OSSproductName$. This
+ migration process is written to simply disable these $AESproductName$ features to make
+ it simpler to roll back, if needed. Alternate, you can isolate the two configurations
+ as described above.
+
+3. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The best way to avoid multiple agents when installing with Helm is to use
+ `--set emissary-ingress.agent.enabled=false` to tell Helm not to install a
+ new Agent with $productName$ $version$. Once testing is done, you can switch
+ Agents safely.
+
+4. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $OSSproductName$ 3.X that use selectors that will match
+ Pods from $AESproductName$ $version$, traffic will be erroneously split between
+ $OSSproductName$ 3.X and $AESproductName$ $version$. The labels used by $AESproductName$
+ $version$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $AESproductName$ $version$ in a separate cluster](../../../../migrate-to-3-alternate/).
+This permits absolute certainty that your $OSSproductName$ $version$ configuration will not be
+affected by changes meant for $AESproductName$ $version$, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is a six-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $AESproductName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $OSSproductName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $AESproductName$ $version$.**
+
+
+ $productName$ requires a valid license or cloud connect token to start. You can refer to the quickstart guide
+ for instructions on how to obtain a free community license. Copy the cloud token command from the guide in Ambassador cloud for use below. If you already have a cloud connect token or
+ a valid enterprise license, then you can skip this step.
+
+
+ After installing the new CRDs, you need to install $AESproductName$ $version$ itself
+ **in the same namespace as your existing $OSSproductName$ $version$ installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+
+ Make sure that you set the various `create` flags when running Helm. This prevents
+ $AESproductName$ $version$ from trying to configure filters that will adversely affect
+ $OSSproductName$ $version$.
+
+
+ Start by making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Typically, $OSSproductName$ $version$ was installed in the `emissary` namespace. If you installed
+ $OSSproductName$ $version$ in a different namespace, change the namespace in the commands below.
+
+ - If you do not need to set `AMBASSADOR_LABEL_SELECTOR`:
+
+ ```bash
+ helm install -n emissary \
+ --set emissary-ingress.agent.enabled=false \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n emissary deployment/edge-stack -w
+ ```
+
+ - If you do need to set `AMBASSADOR_LABEL_SELECTOR`, use `--set`, for example:
+
+ ```bash
+ helm install -n emissary \
+ --set emissary-ingress.agent.enabled=false \
+ --set emissary-ingress.env.AMBASSADOR_LABEL_SELECTOR="version-two=true" \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n emissary deployment/edge-stack -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $AESproductName$ $version$.
+
+
+3. **Test!**
+
+ Your $AESproductName$ $version$ installation should come up running with the configuration
+ resources used by $OSSproductName$ $version$, including `Listener`s and `Host`s.
+
+
+ If you find that your $AESproductName$ $version$ installation and your $OSSproductName$ $version$
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 1, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your $OSSproductName$ $version$ CRDs
+ and delete your installation of $AESproductName$ $version$.
+
+4. **When ready, switch over to $AESproductName$ $version$.**
+
+ You can run $OSSproductName$ $version$ and $AESproductName$ $version$ side-by-side as long as you care
+ to. When you're ready to have $AESproductName$ $version$ handle traffic on its own, switch
+ your original $OSSproductName$ $version$ Service to point to $AESproductName$ $version$. Use
+ `kubectl edit -n emissary service emissary-ingress` and change the `selectors` to:
+
+ ```yaml
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+5. **Install the $productName$ $version$ Ambassador Agent.**
+
+ First, scale the $OSSproductName$ agent to 0:
+
+ ```bash
+ kubectl scale -n emissary deployment/emissary-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent:
+
+ ```bash
+ helm upgrade -n emissary \
+ --set emissary-ingress.agent.enabled=true \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n emissary-ingress deployment/edge-stack -w
+ ```
+
+6. **Finally, enable ACME and filtering in $productName$ $version$.**
+
+ First, scale the $OSSproductName$ Deployment to 0:
+
+ ```bash
+ kubectl scale -n emissary deployment/emissary --replicase=0
+ ```
+
+ Once that's done, enable ACME and filtering in $productName$ $version$:
+
+ ```bash
+ helm upgrade -n emissary \
+ --set emissary-ingress.agent.enabled=true
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n emissary deployment/edge-stack -w
+ ````
+
+Congratulations! At this point, $productName$ $version$ is fully running, and
+it's safe to remove the old `emissary` and `emissary-agent` Deployments:
+
+```bash
+kubectl delete -n emissary deployment/emissary deployment/emissary-agent
+```
+
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-1.14/edge-stack-2.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-1.14/edge-stack-2.X.md
new file mode 100644
index 000000000..3dee2c343
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-1.14/edge-stack-2.X.md
@@ -0,0 +1,354 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 1.14.X to $productName$ $versionTwoX$ (YAML)
+
+
+ This guide covers migrating from $productName$ 1.14.X to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+We're pleased to introduce $productName$ $versionTwoX$! The 2.X family introduces a number of
+changes to allow $productName$ to more gracefully handle larger installations (including
+multitenant or multiorganizational installations), reduce memory footprint, and improve
+performance. In keeping with [SemVer](https://semver.org), $productName$ 2.X introduces
+some changes that aren't backward-compatible with 1.X. These changes are detailed in
+[Major Changes in $productName$ 2.X](../../../../../../about/changes-2.x).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $productName$ 1.14 and $productName$
+$versionTwoX$ side-by-side in the same cluster. This gives $productName$ $versionTwoX$
+and $productName$ 1.14 access to all the same configuration resources, with some
+important caveats:
+
+1. **$productName$ 1.14 will not see any `getambassador.io/v3alpha1` resources.**
+
+ This is intentional; it provides a way to apply configuration only to
+ $productName$ $versionTwoX$, while not interfering with the operation of your
+ $productName$ 1.14 installation.
+
+2. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $productName$ $versionTwoX$ installation from
+ seeing a particular bit of $productName$ 1.14 configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $productName$ $versionTwoX$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $productName$ $versionTwoX$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+3. **$productName$ 1.14 must remain in control of ACME while both installations are running.**
+
+ The processes that handle ACME challenges cannot be managed by both $productName$
+ 1.X and $productName$ $versionTwoX$ at the same time. The instructions below disable ACME
+ in $productName$ $versionTwoX$, allowing $productName$ 1.14 to continue managing it.
+
+ This implies that any new `Host`s used for $productName$ 1.14 should be created using
+ `getambassador.io/v2` so that $productName$ 1.14 can see them.
+
+4. **Check `AuthService` and `RateLimitService` resources, if any.**
+
+ If you have an [`AuthService`](../../../../../using/authservice/) or
+ [`RateLimitService`](../../../../../running/services/rate-limit-service) installed, make
+ sure that they are using the [namespace-qualified DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services).
+ If they are not, the initial migration tests may fail.
+
+ Additionally, when installing with Helm, you must make sure that $productName$ $versionTwoX$
+ does not attempt to create duplicate `AuthService` and `RateLimitService` entries. Add
+
+ ```
+ --set rateLimit.create=false
+ ```
+
+ and
+
+ ```
+ --set authService.create=false
+ ```
+
+ on the Helm command line to prevent duplicating these resources.
+
+5. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The migration YAML used below to install $productName$ $versionTwoX$ will not
+ install a duplicate agent. If you are building your own YAML, make sure not
+ to include a duplicate agent.
+
+6. **If you use ACME for multiple `Host`s, add a wildcard `Host` too.**
+
+ This is required to manage a known issue. This issue will be resolved in a future
+ $AESproductName$ release.
+
+7. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $productName$ 1.14 that use selectors that will match
+ Pods from $productName$ $versionTwoX$, traffic will be erroneously split between
+ $productName$ 1.14 and $productName$ $versionTwoX$. The labels used by $productName$
+ $versionTwoX$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $productName$ $versionTwoX$ in a separate cluster](../../../../migrate-to-2-alternate).
+This permits absolute certainty that your $productName$ 1.14 configuration will not be
+affected by changes meant for $productName$ $versionTwoX$, and it eliminates concerns about
+ACME, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is an eight-step process:
+
+1. **Make sure that older configuration resources are not present.**
+
+ $productName$ 2.X does not support `getambassador.io/v0` or `getambassador.io/v1`
+ resources, and Kubernetes will not permit removing support for CRD versions that are
+ still in use for stored resources. To verify that no resources older than
+ `getambassador.io/v2` are active, run
+
+ ```
+ kubectl get crds -o 'go-template={{range .items}}{{.metadata.name}}={{.status.storedVersions}}{{"\n"}}{{end}}' | fgrep getambassador.io
+ ```
+
+ If `v1` is present in the output, **do not begin migration.** The old resources must be
+ converted to `getambassador.io/v2` and the `storedVersion` information in the cluster
+ must be updated. If necessary, contact Ambassador Labs on [Slack](http://a8r.io/slack)
+ for more information.
+
+2. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you must configure your
+ Kubernetes cluster to support its new `getambassador.io/v3alpha1` configuration
+ resources. Note that `getambassador.io/v2` resources are still supported, but **you
+ must install support for `getambassador.io/v3alpha1`** to run $productName$ $versionTwoX$,
+ even if you intend to continue using only `getambassador.io/v2` resources for some
+ time.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, you need to install $productName$ $versionTwoX$ itself
+ **in the same namespace as your existing $productName$ 1.14 installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+ We publish three manifests for different namespaces. Use only the one that
+ matches the namespace into which you installed $productName$ 1.14:
+
+ - [`aes-emissaryns-migration.yaml`] for the `emissary` namespace;
+ - [`aes-defaultns-migration.yaml`] for the `default` namespace; and
+ - [`aes-ambassadorns-migration.yaml`] for the `ambassador` namespace.
+
+ All three files are set up as follows:
+
+ - They set the `AES_ACME_LEADER_DISABLE` environment variable to prevent $productName$ $versionTwoX$
+ from trying to manage ACME (leaving $productName$ 1.14 to do it instead).
+ - They do NOT set `AMBASSADOR_LABEL_SELECTOR`.
+ - They do NOT install the Ambassador Agent.
+ - They do NOT create an `AuthService` or a `RateLimitService`. It is very important that $productName$
+ $versionTwoX$ not attempt to create these resources, as they are already provided for your $productName$
+ 1.14 installation.
+
+ If any of these do not match your situation, download [`aes-ambassadorns-migration.yaml`] and edit it
+ as needed.
+
+ [`aes-emissaryns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-emissaryns-migration.yaml
+ [`aes-defaultns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-defaultns-migration.yaml
+ [`aes-ambassadorns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-ambassadorns-migration.yaml
+
+ Assuming you're using the `ambassador` namespace, as was typical for $productName$ 1.14:
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-ambassadorns-migration.yaml && \
+ kubectl rollout status -n ambassador deployment/aes -w
+ ```
+
+
+ Make sure that at most one installation of $productName$ is running without setting
+ the AES_ACME_LEADER_DISABLE flag. This prevents collisions in trying to manage
+ ACME.
+
+
+4. **Install `Listener`s and `Host`s as needed.**
+
+ An important difference between $productName$ 1.14 and $productName$ $versionTwoX$ is the
+ new **mandatory** `Listener` CRD. Also, when running both installations side by side,
+ you will need to make sure that a `Host` is present for the new $productName$ $versionTwoX$
+ Service. For example:
+
+ ```bash
+ kubectl apply -f - <
+ Make sure that any Hosts you create use API version getambassador.io/v2,
+ so that they can be managed by $productName$ 1.14 as long as both installations are running.
+
+
+ This example requires that you know the hostname for the $productName$ Service (`$EMISSARY_HOSTNAME`)
+ and that you have created a TLS Secret for it in `$EMISSARY_TLS_SECRET`.
+
+5. **Test!**
+
+ Your $productName$ $versionTwoX$ installation can support the `getambassador.io/v2`
+ configuration resources used by $productName$ 1.14, but you may need to make some
+ changes to the configuration, as detailed in the documentation on
+ [configuring $productName$ Communications](../../../../../../howtos/configure-communications)
+ and [updating CRDs to `getambassador.io/v3alpha1`](../../../../convert-to-v3alpha1).
+
+
+ Kubernetes will not allow you to have a getambassador.io/v3alpha1 resource
+ with the same name as a getambassador.io/v2 resource or vice versa: only
+ one version can be stored at a time.
+
+ If you find that your $productName$ $versionTwoX$ installation and your $productName$ 1.14
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 2, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your 1.14 CRDs, delete your
+ installation of $productName$ $versionTwoX$, and delete the `emissary-system` namespace.
+
+6. **When ready, switch over to $productName$ $versionTwoX$.**
+
+ You can run $productName$ 1.14 and $productName$ $versionTwoX$ side-by-side as long as you care
+ to. However, taking full advantage of $productName$ 2.X's capabilities **requires**
+ [updating your configuration to use `getambassador.io/v3alpha1` configuration resources](../../../../convert-to-v3alpha1),
+ since some useful features in $productName$ $versionTwoX$ are only available using
+ `getambassador.io/v3alpha1` resources.
+
+ When you're ready to have $productName$ $versionTwoX$ handle traffic on its own, switch
+ your original $productName$ 1.14 Service to point to $productName$ $versionTwoX$. Use
+ `kubectl edit -n ambassador service ambassador` and change the `selectors` to:
+
+ ```
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit -n ambassador service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+7. **Install the $productName$ $versionTwoX$ Ambassador Agent.**
+
+ First, scale the 1.14 agent to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent:
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-ambassadorns-agent.yaml && \
+ kubectl rollout status -n ambassador deployment/edge-stack-agent -w
+ ```
+
+8. **Finally, enable ACME in $productName$ $versionTwoX$.**
+
+ First, scale the 1.14 Ambassador to 0:
+
+ ```
+ kubectl scale -n ambassador deployment/ambassador --replicase=0
+ ```
+
+ Once that's done, enable ACME in $productName$ $versionTwoX$:
+
+ ```bash
+ kubectl set env -n ambassador deployment/aes AES_ACME_LEADER_DISABLE-
+ kubectl rollout status -n ambassador deployment/aes -w
+ ````
+
+Congratulations! At this point, $productName$ $versionTwoX$ is fully running, and
+it's safe to remove the `ambassador` and `ambassador-agent` Deployments:
+
+```
+kubectl delete -n ambassador deployment/ambassador deployment/ambassador-agent
+```
+
+Once $productName$ 1.14 is no longer running, you may [convert](../../../../convert-to-v3alpha1)
+any remaining `getambassador.io/v2` resources to `getambassador.io/v3alpha1`.
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.0/edge-stack-2.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.0/edge-stack-2.X.md
new file mode 100644
index 000000000..23723ab84
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.0/edge-stack-2.X.md
@@ -0,0 +1,78 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.0.5 to $productName$ $versionTwoX$ (YAML)
+
+
+ This guide covers migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+
+ Upgrading from $productName$ 2.0.5 to $productName$ $versionTwoX$ typically requires downtime.
+ In some situations, Ambassador Labs Support may be able to assist with a zero-downtime migration;
+ contact support with questions.
+
+
+Migrating from $productName$ 2.0.5 to $productName$ $versionTwoX$ is a three-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Delete $productName$ 2.0.5 Deployment.**
+
+
+ Delete only the Deployment for $productName$ 2.0.5 in order to preserve all of
+ your existing configuration.
+
+
+ Use `kubectl` to delete the Deployment for $productName$ 2.0.5. Typically, this will be found
+ in the `ambassador` namespace.
+
+ ```
+ kubectl delete -n ambassador deployment edge-stack
+ ```
+
+3. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $versionTwoX$. This will install
+ in the `$productNamespace$` namespace. If necessary for your installation (e.g. if you were
+ running with `AMBASSADOR_SINGLE_NAMESPACE` set), you can download `aes.yaml` and edit as
+ needed.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.4/edge-stack-2.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.4/edge-stack-2.X.md
new file mode 100644
index 000000000..d8bde7af6
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.4/edge-stack-2.X.md
@@ -0,0 +1,60 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.4.X to $productName$ $versionTwoX$ (YAML)
+
+
+ This guide covers migrating from $productName$ 2.4 to $productName$ $versionTwoX$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between minor
+versions is straightforward.
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $versionTwoX$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $versionTwoX$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $versionTwoX$.**
+
+ After installing the new CRDs, upgrade $productName$ $versionTwoX$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$versionTwoX$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md
new file mode 100644
index 000000000..948704ed1
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md
@@ -0,0 +1,127 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 2.5.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 2.5.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.24.1. Envoy has removed support for the Envoy V2 Transport Protocol and it is no longer valid. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally, support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes. Here are a few items that need to be checked or addressed before upgrading:
+
+1. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+2. Check removed runtime flags for behavior changes that may affect you:
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+3. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md
new file mode 100644
index 000000000..d342f175b
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md
@@ -0,0 +1,71 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.4.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 3.4.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+### Resources to check before migrating to $version$.
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read below before upgrading.
+
+
+$productName$ 3.4 has been upgraded from Envoy 1.23 to Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.7/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.7/edge-stack-3.X.md
new file mode 100644
index 000000000..435cc9dcc
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.7/edge-stack-3.X.md
@@ -0,0 +1,63 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.7.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 3.7.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md
new file mode 100644
index 000000000..819df4573
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md
@@ -0,0 +1,63 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.8.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 3.8.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/latest/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md b/docs/edge-stack/latest/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md
new file mode 100644
index 000000000..0995e73cc
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md
@@ -0,0 +1,252 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $OSSproductName$ $version$ to $AESproductName$ $version$ (YAML)
+
+
+ This guide covers migrating from $OSSproductName$ $version$ to $AESproductName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+You can upgrade from $OSSproductName$ to $AESproductName$ with a few simple commands. When you upgrade to $AESproductName$, you'll be able to access additional capabilities such as **automatic HTTPS/TLS termination, Swagger/OpenAPI support, API catalog, Single Sign-On, and more.** For more about the differences between $AESproductName$ and $OSSproductName$, see the [Editions page](/editions).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $OSSproductName$ $version$ and $AESproductName$
+$version$ side-by-side in the same cluster. This gives $AESproductName$ $version$
+and $AESproductName$ $version$ access to all the same configuration resources, with some
+important notes:
+
+1. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $AESproductName$ $version$ installation from
+ seeing a particular bit of $OSSproductName$ $version$ configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $AESproductName$ $version$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $AESproductName$ $version$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+2. **$AESproductName$ ACME and `Filter`s will be disabled while $OSSproductName$ is still running.**
+
+ Since $AESproductName$ and $OSSproductName$ share configuration, $AESproductName$ cannot
+ configure its ACME or other filter processors without also affecting $OSSproductName$. This
+ migration process is written to simply disable these $AESproductName$ features to make
+ it simpler to roll back, if needed. Alternate, you can isolate the two configurations
+ as described above.
+
+3. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The best way to avoid multiple agents when installing with Helm is to use
+ `--set emissary-ingress.agent.enabled=false` to tell Helm not to install a
+ new Agent with $productName$ $version$. Once testing is done, you can switch
+ Agents safely.
+
+4. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $OSSproductName$ 3.X that use selectors that will match
+ Pods from $AESproductName$ $version$, traffic will be erroneously split between
+ $OSSproductName$ 2.4 and $AESproductName$ $version$. The labels used by $AESproductName$
+ $version$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $AESproductName$ $version$ in a separate cluster](../../../../migrate-to-3-alternate/).
+This permits absolute certainty that your $OSSproductName$ $version$ configuration will not be
+affected by changes meant for $AESproductName$ $version$, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is a six-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $AESproductName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $OSSproductName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $AESproductName$ $version$.**
+
+ After installing the new CRDs, you need to install $AESproductName$ $version$ itself
+ **in the same namespace as your existing $OSSproductName$ $version$ installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+ We publish three manifests for different namespaces. Use only the one that
+ matches the namespace into which you installed $OSSproductName$ $version$:
+
+ - [`aes-emissaryns-migration.yaml`] for the `emissary` namespace;
+ - [`aes-defaultns-migration.yaml`] for the `default` namespace; and
+ - [`aes-ambassadorns-migration.yaml`] for the `ambassador` namespace.
+
+ All three files are set up as follows:
+
+ - They set the `AES_ACME_LEADER_DISABLE` environment variable; you'll enable ACME towards the end of
+ the migration.
+ - They do NOT create any `AuthService` or a `RateLimitService`, since your $OSSproductName$ may have
+ these defined. Again, you'll manage these at the end of migration.
+ - They do NOT set `AMBASSADOR_LABEL_SELECTOR`.
+ - They do NOT install the Ambassador Agent, since there is already an Ambassador Agent running for
+ $OSSproductName$ $version$.
+
+ If any of these do not match your situation, download [`aes-emissaryns-migration.yaml`] and edit it
+ as needed.
+
+ [`aes-emissaryns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$version$/aes-emissaryns-migration.yaml
+ [`aes-defaultns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$version$/aes-defaultns-migration.yaml
+ [`aes-ambassadorns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$version$/aes-ambassadorns-migration.yaml
+
+ Assuming you're using the `emissary` namespace, as was typical for $OSSproductName$ $version$:
+
+ **If you need to set `AMBASSADOR_LABEL_SELECTOR`**, download `aes-emissaryns-migration.yaml` and edit it to
+ do so.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-emissaryns-migration.yaml && \
+ kubectl rollout status -n emissary deployment/aes -w
+ ```
+
+3. **Test!**
+
+ Your $AESproductName$ $version$ installation should come up running with the configuration
+ resources used by $OSSproductName$ $version$, including `Listener`s and `Host`s.
+
+
+ If you find that your $AESproductName$ $version$ installation and your $OSSproductName$ $version$
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 1, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your $OSSproductName$ $version$ CRDs
+ and delete your installation of $AESproductName$ $version$.
+
+4. **When ready, switch over to $AESproductName$ $version$.**
+
+ You can run $OSSproductName$ $version$ and $AESproductName$ $version$ side-by-side as long as you care
+ to. When you're ready to have $AESproductName$ $version$ handle traffic on its own, switch
+ your original $OSSproductName$ $version$ Service to point to $AESproductName$ $version$. Use
+ `kubectl edit -n emissary service emissary-ingress` and change the `selectors` to:
+
+ ```yaml
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+5. **Install the $productName$ $version$ Ambassador Agent.**
+
+ First, scale the $OSSproductName$ agent to 0:
+
+ ```
+ kubectl scale -n emissary deployment/emissary-ingress-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent:
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-emissaryns-agent.yaml && \
+ kubectl rollout status -n emissary deployment/edge-stack-agent -w
+ ```
+
+6. **Finally, enable ACME and filtering in $productName$ $version$.**
+
+
+ Enabling filtering correctly in $productName$ $version$ requires that no
+ AuthService or RateLimitService resources be present; see
+ below for more.
+
+
+ First, make sure that no `AuthService` or `RateLimitService` resources are present; delete
+ these if necessary.
+
+ - If you are currently using an external authentication service that provides functionality
+ you'll still require, turn it into an [`External` `Filter`] (with a [`FilterPolicy`] to
+ direct requests that need it correctly).
+
+ - If you are currently using a `RateLimitService`, you can set up
+ [Edge Stack Rate Limiting] instead.
+
+ [`External` `Filter`]: ../../../../../../howtos/ext-filters#2-configure-aesproductname-authentication
+ [`FilterPolicy`]: ../../../../../../howtos/ext-filters#2-configure-aesproductname-authentication
+ [Edge Stack Rate Limiting]: ../../../../../using/rate-limits
+
+ After making sure no `AuthService` or `RateLimitService` resources are present, scale the
+ $OSSproductName$ Deployment to 0:
+
+ ```bash
+ kubectl scale -n emissary deployment/emissary-ingress --replicase=0
+ ```
+
+ Once that's done, apply resources specific to $AESproductName$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/resources-migration.yaml
+ ```
+
+ Then, finally, enable ACME and filtering in $productName$ $version$:
+
+ ```bash
+ kubectl set env -n emissary deployment/aes AES_ACME_LEADER_DISABLE-
+ kubectl rollout status -n emissary deployment/aes -w
+ ````
+
+Congratulations! At this point, $productName$ $version$ is fully running, and
+it's safe to remove the old `emissary` and `emissary-agent` Deployments:
+
+```
+kubectl delete -n emissary deployment/emissary deployment/emissary-agent
+```
+
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/latest/topics/install/yaml-install.md b/docs/edge-stack/latest/topics/install/yaml-install.md
new file mode 100644
index 000000000..da4b2d916
--- /dev/null
+++ b/docs/edge-stack/latest/topics/install/yaml-install.md
@@ -0,0 +1,93 @@
+---
+ description: In this guide, we'll walk through the process of deploying $productName$ in Kubernetes for ingress routing.
+---
+
+import Alert from '@material-ui/lab/Alert';
+
+# Install manually
+
+
+
+ To migrate from $productName$ 1.X to $productName$ 2.X, see the
+ [$productName$ migration matrix](../migration-matrix/). This guide
+ **will not work** for that, due to changes to the configuration
+ resources used for $productName$ 2.X.
+
+
+
+In this guide, we'll walk you through installing $productName$ in your Kubernetes cluster.
+
+The manual install process does not allow for as much control over configuration
+as the [Helm install method](../helm), so if you need more control over your $productName$
+installation, it is recommended that you use helm.
+
+## Before you begin
+
+
+ $productName$ requires a valid license or cloud connect token to start. You can refer to the quickstart guide
+ for instructions on how to obtain a free community license. Copy the cloud token command from the guide in Ambassador cloud for use below. If you already have a cloud connect token or
+ a valid enterprise license, then you can skip this step.
+
+
+$productName$ is designed to run in Kubernetes for production. The most essential requirements are:
+
+* Kubernetes 1.11 or later
+* The `kubectl` command-line tool
+
+## Install with YAML
+
+$productName$ is typically deployed to Kubernetes from the command line. If you don't have Kubernetes, you should use our [Docker](../docker) image to deploy $productName$ locally.
+
+1. In your terminal, run the following command:
+
+ ```
+ kubectl create namespace $productNamespace$ || true
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml && \
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl -n $productNamespace$ wait --for condition=available --timeout=90s deploy $productDeploymentName$
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the $productNamespace$ namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. Determine the IP address or hostname of your cluster by running the following command:
+
+ ```
+ kubectl get -n $productNamespace$ service $productDeploymentName$ -o "go-template={{range .status.loadBalancer.ingress}}{{or .ip .hostname}}{{end}}"
+ ```
+
+ Your load balancer may take several minutes to provision your IP address. Repeat the provided command until you get an IP address.
+
+3. Next Steps
+
+ $productName$ shold now be successfully installed and running, but in order to get started deploying Services and test routing to them you need to configure a few more resources.
+
+ - [The `Listener` Resource](../../running/listener/) is required to configure which ports the $productName$ pods listen on so that they can begin responding to requests.
+ - [The `Mapping` Resouce](../../using/intro-mappings/) is used to configure routing requests to services in your cluster.
+ - [The `Host` Resource](../../running/host-crd/) configures TLS termination for enablin HTTPS communication.
+ - Explore how $productName$ [configures communication with clients](../../../howtos/configure-communications)
+
+
+ We strongly recommend following along with our Quickstart Guide to get started by creating a Listener, deploying a simple service to test with, and setting up a Mapping to route requests from $productName$ to the demo service.
+
+
+## Upgrading an existing installation
+
+See the [migration matrix](../migration-matrix) for instructions about upgrading
+$productName$.
diff --git a/docs/edge-stack/latest/topics/running/aes-extensions/authentication.md b/docs/edge-stack/latest/topics/running/aes-extensions/authentication.md
new file mode 100644
index 000000000..79b005a66
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/aes-extensions/authentication.md
@@ -0,0 +1,78 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Authentication extension
+
+Edge Stack ships with an authentication service that is enabled
+to perform OAuth, JWT validation, and custom authentication schemes. It can
+perform different authentication schemes on different requests allowing you to
+enforce authentication as your application needs.
+
+The Filter and FilterPolicy resources are used to [configure how to do authentication](../../../using/filters). This doc focuses on how to deploy and manage the authentication extension.
+
+## Edge Stack configuration
+
+Edge Stack uses the [AuthService plugin](../../services/auth-service)
+to connect to the authentication extension.
+
+The default AuthService is named `ambassador-edge-stack-auth` and is defined
+as:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: AuthService
+metadata:
+ name: ambassador-edge-stack-auth
+ namespace: ambassador
+spec:
+ auth_service: 127.0.0.1:8500
+ proto: grpc
+ status_on_error:
+ code: 503
+ allow_request_body: false
+```
+
+This configures Envoy to talk to the extension process running on port 8500
+using gRPC and trim the body from the request when doing so. The default error
+code of 503 is usually overwritten by the Filter that is authenticating the
+request.
+
+This default AuthService works for most use cases. If you need to
+tune how Edge Stack connects to the authentication extension (like changing the
+default timeout), you can find the full configuration options in the
+[AuthService plugin docs](../../services/auth-service).
+
+## Authentication extension configuration
+
+Certain use cases may require some tuning of the authentication extension.
+Configuration of this extension is managed via environment variables.
+[The Ambassador container](../../environment) has a full list of environment
+variables available for configuration, including the variables used by the
+authentication extension.
+
+#### Redis
+
+The authentication extension uses Redis for caching the response from the
+`token endpoint` when performing OAuth.
+
+Edge Stack shares the same Redis pool for all features that use Redis. More information is available for [tuning Redis](../../aes-redis) if needed.
+
+#### Timeout variables
+
+The `AES_AUTH_TIMEOUT` environment variable configures the default timeout in
+the authentication extension.
+
+This timeout is necessary so that any error responses configured by Filters
+that the extension runs make their way to the client. Otherwise they would be
+overruled by the timeout from Envoy if a request takes longer than five seconds.
+
+If you have a long chain of Filters or a Filter that takes five or more seconds to respond,
+you can increase the timeout value to give your Filters enough time to run.
+
+
+The timeout_ms of the ambassador-edge-stack-auth AuthService defaults
+to a value of 5000 (five seconds). You will need to adjust this as well.
+
+AES_AUTH_TIMEOUT should always be around one second shorter than the timeout_ms of the AuthService to ensure Filter error responses make it to the client.
+
+The External Filter also have a timeout_ms field that must be set if a single Filter will take longer than five seconds.
+
diff --git a/docs/edge-stack/latest/topics/running/aes-extensions/index.md b/docs/edge-stack/latest/topics/running/aes-extensions/index.md
new file mode 100644
index 000000000..df71fcad6
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/aes-extensions/index.md
@@ -0,0 +1,33 @@
+# Ambassador Edge Stack extensions
+
+The Ambassador Edge Stack contains a number of pre-built extensions that make
+running, deploying, and exposing your applications in Kubernetes easier.
+
+Use of AES extensions is implemented via Kubernetes Custom Resources.
+Documentation for how to uses the various extensions can be found throughout the
+[Using AES AES for Developer](../../using/) section of the docs. This section
+is concerned with how to operate and tune deployment of these extensions in AES.
+
+## Redis
+
+Since AES does not use a database, Redis is uses for caching state information
+when an extension requires it.
+
+The Ambassador Edge Stack shares the same Redis pool for all features that use
+Redis.
+
+The [Redis documentation](../aes-redis) contains detailed information on tuning
+how AES talks to Redis.
+
+## The Extension process
+
+The various extensions of the Ambassador Edge Stack run as a separate process
+from the Ambassador control plane and Envoy data plane.
+
+### `AES_LOG_LEVEL`
+
+The `AES_LOG_LEVEL` controls the logging of all of the extensions in AES.
+
+Log level names are case-insensitive. From least verbose to most
+verbose, valid log levels are `error`, `warn`/`warning`, `info`,
+`debug`, and `trace`.
diff --git a/docs/edge-stack/latest/topics/running/aes-extensions/ratelimit.md b/docs/edge-stack/latest/topics/running/aes-extensions/ratelimit.md
new file mode 100644
index 000000000..51d2c6210
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/aes-extensions/ratelimit.md
@@ -0,0 +1,92 @@
+# Rate limiting extension
+
+The Ambassador Edge Stack ships with a rate limiting service that is enabled
+to perform advanced rate limiting out of the box.
+
+Configuration of the `Mapping` and `RateLimit` resources that control **how**
+to rate limit requests can be found in the
+[Rate Limiting](../../../using/rate-limits) section of the documentation.
+
+This document focuses on how to deploy and manage the rate limiting extension.
+
+## Ambassador configuration
+
+Ambassador uses the [`RateLimitService` plugin](../../services/rate-limit-service)
+to connect to the rate limiting extension in the Ambassador Edge Stack.
+
+The default `RateLimitService` is named `ambassador-edge-stack-ratelimit` and is
+defined as:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimitService
+metadata:
+ name: ambassador-edge-stack-ratelimit
+ namespace: ambassador
+spec:
+ service: 127.0.0.1:8500
+ failure_mode_deny: false # when set to true envoy will return 500 error when unable to communicate with RateLimitService
+ grpc:
+ use_resource_exhausted_code: true # default is false
+```
+
+- `failure_mode_deny` By default, $productName$ will fail open when unable to communicate with the service due to it becoming unvailable or due to timeouts. When this happens the upstream service that is being protected by a rate limit may be overloaded due to this behavior. When set to `true` $productName$ will be configured to return a `500` status code when it is unable to communicate with the RateLimit service and will fail closed by rejecting request to the upstream service.
+- `grpc` contains settings for grpc connections
+ - `use_resource_exhausted_code` By default, $productName$ will return an `UNAVAILABLE` gRPC code when a request is rate limited.
+ When set to `true`, this field will cause $productName$ will return a `RESOURCE_EXHAUSTED` gRPC code instead.
+
+This configures Envoy to send requests that are labeled for rate limiting to the
+extension process running on port 8500. The rate limiting extension will then
+use that request to count against any `RateLimit` whose pattern matches the
+request labels.
+
+## Authentication extension configuration
+
+Certain use cases may require some tuning of the rate limiting extension.
+Configuration of this extension is managed via environment variables.
+[The Ambassador Container](../../environment) has a full list of environment
+variables available for configuration. This document highlights the ones used
+by the rate limiting extension.
+
+### Redis
+
+The rate limiting extension relies heavily on redis for writing and reading
+counters for the different `RateLimit` patterns.
+
+The Ambassador Edge Stack shares the same Redis pool for all features that use
+Redis.
+
+See the [Redis documentation](../../aes-redis) for information on Redis tuning.
+
+#### REDIS_PERSECOND
+
+If `REDIS_PERSECOND` is true, a second Redis connection pool is created (to a
+potentially different Redis instance) that is only used for per-second
+RateLimits; this second connection pool is configured by the `REDIS_PERSECOND_*`
+variables rather than the usual `REDIS_*` variables.
+
+#### `AES_RATELIMIT_PREVIEW`
+
+Set `AES_RATELIMIT_PREVIEW` to `true` to access support for redis clustering,
+local caching, and an upgraded redis client with improved scalability in
+preview mode.
+
+#### `LOCAL_CACHE_SIZE_IN_BYTES`
+
+**Only available if `AES_RATELIMIT_PREVIEW: "true`.**
+
+The AES rate limit extension can optionally cache over-the-limit keys so it does
+not need to read the redis cache again for requests with labels that are already
+over the limit.
+
+Setting `LOCAL_CACHE_SIZE_IN_BYTES` to a non-zero value with enable local
+caching.
+
+#### `NEAR_LIMIT_RATIO`
+
+**Only available if `AES_RATELIMIT_PREVIEW: "true"`**
+
+Adjusts the ratio used by the `near_limit` statistic for tracking requests that
+are "near the limit".
+
+Defaults to `0.8` (80%) of the limit defined in the `RateLimit` rule.
diff --git a/docs/edge-stack/latest/topics/running/aes-redis.md b/docs/edge-stack/latest/topics/running/aes-redis.md
new file mode 100644
index 000000000..fe72b03be
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/aes-redis.md
@@ -0,0 +1,247 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Edge Stack and Redis
+
+The Ambassador Edge Stack make use of Redis for several purposes. By default,
+all components of the Ambassador Edge Stack share a Redis connection pool.
+
+## Rate Limit Service
+
+The rate limiting service, can be configured to use different connection pools
+for handling per-second rate limits or connecting to Redis clusters.
+
+### AES_RATELIMIT_PREVIEW
+
+Set `AES_RATELIMIT_PREVIEW` to `true` to access support for redis clustering,
+local caching, and an upgraded redis client with improved scalability in
+preview mode.
+
+### REDIS_PERSECOND
+
+If `REDIS_PERSECOND` is true, a second Redis connection pool is created (to a
+potentially different Redis instance) that is only used for per-second
+RateLimits; this second connection pool is configured by the `REDIS_PERSECOND_*`
+variables rather than the usual `REDIS_*` variables.
+
+## Redis layer 4 connectivity (L4)
+
+#### `SOCKET_TYPE`
+
+The Go network to use to talk to Redis. see [Go net.Dial](https://golang.org/src/net/dial.go)
+
+Most users will leave this as the default of `tcp`.
+
+#### `URL`
+
+The URL to dial to talk to Redis
+
+This will be either a hostname:port pair or a comma separated list of
+hostname:port pairs depending on the [TYPE](#redis-type) you are using.
+
+For `REDIS_URL` (but not `REDIS_PERSECOND_URL`), not setting a value disables
+Ambassador Edge Stack features that require Redis.
+
+#### `TLS_ENABLED`
+
+Specifies whether to use TLS when talking to Redis.
+
+#### `TLS_INSECURE`
+
+Specifies whether to skip certificate verification when
+using TLS to talk to Redis.
+
+Consider [installing the self-signed certificate for your Redis in to the
+Ambassador Edge Stack container](../../using/filters/#filters-using-self-signed-certificates)
+in order to leave certificate verification on.
+
+## Redis authentication (auth)
+
+**Default**
+
+Configure authentication to a redis pool using the default implementation.
+
+#### `PASSWORD`
+
+If set, it is used to [AUTH](https://redis.io/commands/auth) to Redis immediately after the connection is
+established.
+
+#### `USERNAME`
+
+If set, then that username is used with the password to log in as that user in
+the [Redis 6 ACL](https://redis.io/docs/manual/security/acl/). It is invalid to set a username without setting a
+password. It is invalid to set a username with Redis 5 or lower.
+
+The following YAML snippet is an example of configuring Redis authentication in the Ambassador deployment's environment variables.
+
+```yaml
+env:
+- name: REDIS_USERNAME:
+ value: "default"
+- name: REDIS_PASSWORD:
+ valueFrom:
+ secretKeyRef:
+ key: password
+ name: ambassador-redis-password
+```
+
+
+ This example demonstrates getting the redis password from a secret called ambassador-redis-password instead
+ of providing the value directly.
+
+
+**Rate Limit Preview**
+
+Configure authentication to a redis pool using the preview rate limiting
+implementation
+
+#### `AUTH`
+
+Required for authentication with Rate Limit Preview. You must also configure `REDIS_USERNAME`
+and `REDIS_PASSWORD` for the rest of Ambassador's Redis usage.
+
+If you configure `REDIS_AUTH`, then `REDIS_USERNAME` cannot be changed from the value `default`, and
+`REDIS_PASSWORD` should contain the same value as `REDIS_AUTH`.
+
+`REDIS_USERNAME` and `REDIS_PASSWORD` handle all Redis authentication that is separate from Rate Limit Preview so
+failing to set them when using `REDIS_AUTH` will result in Ambassador not being able to authenticate with Redis for
+all of its other functionality.
+
+Adding `AUTH` to the example above for rate limit preview would look like the following snippet.
+
+```yaml
+env:
+- name: REDIS_USERNAME:
+ value: "default"
+- name: REDIS_PASSWORD:
+ valueFrom:
+ secretKeyRef:
+ key: password
+ name: ambassador-redis-password
+- name: REDIS_AUTH
+ valueFrom:
+ secretKeyRef:
+ key: password
+ name: ambassador-redis-password
+```
+
+
+ Setting AUTH without USERNAME and PASSWORD can result in various problems since AUTH does not
+ overwrite the basic Redis authentication behavior for systems outside of rate limit preview.
+
+
+## Redis performance tuning (tune)
+
+#### `POOL_SIZE`
+
+The number of connections to keep around when idle.
+
+The total number of connections may go lower than this if there are errors.
+
+The total number of connections may go higher than this during a load surge.
+
+#### `PING_INTERVAL`
+
+The rate at which Ambassador will ping the idle connections in the normal pool
+(not extra connections created for a load surge).
+
+Ambassador will `PING` one of them every `PING_INTERVAL÷POOL_SIZE` so
+that each connection will on average be `PING`ed every `PING_INTERVAL`.
+
+#### `TIMEOUT`
+
+Sets 4 different timeouts:
+
+1. `(*net.Dialer).Timeout` for establishing connections
+2. `(*redis.Client).ReadTimeout` for reading a single complete response
+3. `(*redis.Client).WriteTimeout` for writing a single complete request
+4. The timeout when waiting for a connection to become available from the
+ pool (not including the dial time, which is timed out separately)
+
+A value of "0" means "no timeout".
+
+#### `SURGE_LIMIT_INTERVAL`
+
+During a load surge, if the pool is depleted, then Ambassador may create new
+connections to Redis in order to fulfill demand, at a maximum rate of one new
+connection per `SURGE_LIMIT_INTERVAL`.
+
+A value of "0" (the default) means "allow new connections to be created as
+fast as necessary.
+
+The total number of connections that Ambassador can surge to is unbounded.
+
+#### `SURGE_LIMIT_AFTER`
+
+The number of connections that can be created _after_ the normal pool is
+depleted before `SURGE_LIMIT_INTERVAL` kicks in.
+
+The first `POOL_SIZE+SURGE_LIMIT_AFTER` connections are allowed to
+be created as fast as necessary.
+
+This setting has no effect if `SURGE_LIMIT_INTERVAL` is 0.
+
+#### `SURGE_POOL_SIZE`
+
+Normally during a surge, excess connections beyond `POOL_SIZE` are
+closed immediately after they are done being used, instead of being returned
+to a pool.
+
+`SURGE_POOL_SIZE` configures a "reserve" pool for excess connections
+created during a surge.
+
+Excess connections beyond `POOL_SIZE+SURGE_POOL_SIZE` will still
+be closed immediately after use.
+
+#### `SURGE_POOL_DRAIN_INTERVAL`
+
+How quickly to drain connections from the surge pool after a surge is over.
+
+Connections are closed at a rate of one connection per
+`SURGE_POOL_DRAIN_INTERVAL`.
+
+This setting has no effect if `SURGE_POOL_SIZE` is 0.
+
+## Redis type
+
+Redis currently support three different deployment methods. Ambassador Edge
+Stack can now support using a Redis deployed in any of these ways for rate
+limiting when `AES_RATELIMIT_PREVIEW=true`.
+
+#### `TYPE`
+
+- `SINGLE`: Talk to a single instance of redis, or a redis proxy.
+
+ Requires the redis `REDIS_URL` or `REDIS_PERSECOND_URL` to be either a
+ single hostname:port pair or a unix domain socket reference.
+
+- `SENTINEL`: Talk to a redis deployment with sentinel instances (see
+ https://redis.io/topics/sentinel).
+
+ Requires the redis `REDIS_URL` or `REDIS_PERSECOND_URL` to be a comma
+ separated list with the first string as the master name of the sentinel
+ cluster followed by hostname:port pairs. The list size should be >= 2.
+ The first item is the name of the master and the rest are the sentinels.
+
+- `CLUSTER`: Talk to a redis in cluster mode (see
+ https://redis.io/topics/cluster-spec)
+
+ Requires the redis `REDIS_URL` or `REDIS_PERSECOND_URL` to be either a
+ single hostname:port pair of the read/write endpoint or a comma separated
+ list of hostname:port pairs with all the nodes in the cluster.
+
+ `PIPELINE_WINDOW` must be set when `TYPE: CLUSTER`.
+
+#### `PIPELINE_WINDOW`
+
+The duration after which internal pipelines will be flushed.
+
+If window is zero then implicit pipelining will be disabled.
+
+> `150us` is recommended when using implicit pipelining in production.
+
+#### `PIPELINE_LIMIT`
+
+The maximum number of commands that can be pipelined before flushing.
+
+If limit is zero then no limit will be used and pipelines will only be limited
+by the specified time window.
diff --git a/docs/edge-stack/latest/topics/running/ambassador.md b/docs/edge-stack/latest/topics/running/ambassador.md
new file mode 100644
index 000000000..fb8a5fa4e
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/ambassador.md
@@ -0,0 +1,619 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Ambassador** **Module** Resource
+
+
+
+If present, the `ambassador` `Module` defines system-wide configuration for $productName$. **You may very well not need this resource.** To use the `ambassador` `Module` to configure $productName$, it MUST be named `ambassador`, otherwise it will be ignored. To create multiple `ambassador` `Module`s in the same Kubernetes namespace, you will need to apply them as annotations with separate `ambassador_id`s: you will not be able to use multiple CRDs.
+
+The defaults in the `ambassador` `Module` are:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+# Use ambassador_id only if you are using multiple instances of $productName$ in the same cluster.
+# See below for more information.
+ ambassador_id: [ "" ]
+ config:
+ # Use the items below for config fields
+```
+
+There are many config field items that can be configured on the `ambassador` `Module`. They are listed below with examples and grouped by category.
+
+## Envoy
+
+##### Content-Length headers
+
+* `allow_chunked_length: true` tells Envoy to allow requests or responses with both `Content-Length` and `Transfer-Encoding` headers set.
+
+By default, messages with both `Content-Length` and `Content-Transfer-Encoding` are rejected. If `allow_chunked_length` is `true`, $productName$ will remove the `Content-Length` header and process the message. See the [Envoy documentation for more details](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto.html?highlight=allow_chunked_length#config-core-v3-http1protocoloptions).
+
+##### Envoy access logs
+
+* `envoy_log_path` defines the path of Envoy's access log. By default this is standard output.
+* `envoy_log_type` defines the type of access log Envoy will use. Currently, only `json` or `text` are supported.
+* `envoy_log_format` defines the Envoy access log line format.
+
+These logs can be formatted using [Envoy operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) to display specific information about an incoming request. The example below will show only the protocol and duration of a request:
+
+```yaml
+envoy_log_path: /dev/fd/1
+envoy_log_type: json
+envoy_log_format:
+ {
+ "protocol": "%PROTOCOL%",
+ "duration": "%DURATION%"
+ }
+```
+
+See the Envoy documentation for the [standard log format](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#default-format-string) and a [complete list of log format operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/access_log).
+
+##### Envoy validation timeout
+
+* `envoy_validation_timeout` defines the timeout, in seconds, for validating a new Envoy configuration.
+
+The default is 10; a value of 0 disables Envoy configuration validation. Most installations will not need to use this setting.
+
+For example:
+
+```yaml
+envoy_validation_timeout: 30
+```
+
+would allow 30 seconds to validate the generated Envoy configuration.
+
+##### Error response overrides
+
+* `error_response_overrides` permits changing the status code and body text for 4XX and 5XX response codes.
+
+By default, $productName$ will pass through error responses without modification, and errors generated locally will use Envoy's default response body, if any.
+
+See [using error response overrides](../custom-error-responses) for usage details. For example, this configuration:
+
+```yaml
+error_response_overrides:
+ - on_status_code: 404
+ body:
+ text_format: "File not found"
+```
+
+would explicitly modify the body of 404s to say "File not found".
+
+##### Forwarding client cert details
+
+Two attributes allow providing information about the client's TLS certificate to upstream certificates:
+
+* `forward_client_cert_details: true` will tell Envoy to add the `X-Forwarded-Client-Cert` to upstream
+ requests.
+* `set_current_client_cert_details` will tell Envoy what information to include in the
+ `X-Forwarded-Client-Cert` header.
+
+$productName$ will not forward information about a certificate that it cannot validate.
+
+See the Envoy documentation on [X-Forwarded-Client-Cert](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html?highlight=xfcc#x-forwarded-client-cert) and [SetCurrentClientCertDetails](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto.html#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-setcurrentclientcertdetails) for more information.
+
+```yaml
+forward_client_cert_details: true
+set_current_client_cert_details: SANITIZE
+```
+
+##### Server name
+
+* `server_name` allows overriding the server name that Envoy sends with responses to clients.
+
+By default, Envoy uses a server name of `envoy`.
+
+##### Suppress Envoy headers
+
+* `suppress_envoy_headers: true` will prevent $productName$ from emitting certain additional
+ headers to HTTP requests and responses.
+
+For the exact set of headers covered by this config, see the [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-headers-set)
+
+---
+## General
+
+##### Ambassador ID
+
+* `ambassador_id` allows using multiple instances of $productName$ in the same cluster.
+
+We recommend _not_ setting `ambassador_id` if you are running only one instance of $productName$ in your cluster. For more information, see the [Running and Deployment documentation](../running/#ambassador_id).
+
+If used, the `ambassador_id` value must be an array, for example:
+
+```yaml
+ambassador_id: [ "test_environment" ]
+```
+
+##### Defaults
+
+* `defaults` provides a dictionary of default values that will be applied to various $productName$ resources.
+
+See [Using `ambassador` `Module` Defaults](../../using/defaults) for more information.
+
+---
+
+## gRPC
+
+##### Bridges
+
+* `enable_grpc_http11_bridge: true` will enable the gRPC-HTTP/1.1 bridge.
+* `enable_grpc_web: true` will enable the gRPC-Web bridge.
+
+gRPC is a binary HTTP/2-based protocol. While this allows high performance, it can be problematic for clients that are unable to speak HTTP/2 (such as JavaScript in many browsers, or legacy clients in difficult-to-update environments).
+
+The gRPC-HTTP/1.1 bridge can translate HTTP/1.1 calls with `Content-Type: application/grpc` into gRPC calls: $productName$ will perform buffering and translation as necessary. For more details on the translation process, see the [Envoy gRPC HTTP/1.1 bridge documentation](https://www.envoyproxy.io/docs/envoy/v1.11.2/configuration/http_filters/grpc_http1_bridge_filter.html).
+
+Likewise, gRPC-Web is a JSON and HTTP-based protocol that allows browser-based clients to take advantage of gRPC protocols. The gRPC-Web specification requires a server-side proxy to translate between gRPC-Web requests and gRPC backend services, and $productName$ can fill this role when the gRPC-Web bridge is enabled. For more details on the translation process, see the [Envoy gRPC HTTP/1.1 bridge documentation](https://www.envoyproxy.io/docs/envoy/v1.11.2/configuration/http_filters/grpc_http1_bridge_filter.html); for more details on gRPC-Web itself, see the [gRPC-Web client GitHub repo](https://github.com/grpc/grpc-web).
+
+##### Statistics
+
+* `grpc_stats` allows enabling telemetry for gRPC calls using Envoy's [gRPC Statistics Filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_stats_filter).
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ grpc_stats:
+ upstream_stats: true
+ services:
+ - name: .
+ method_names: []
+```
+
+Supported parameters:
+* `all_methods`
+* `services`
+* `upstream_stats`
+
+Available metrics:
+* `envoy_cluster_grpc__`
+* `envoy_cluster_grpc__request_message_count`
+* `envoy_cluster_grpc__response_message_count`
+* `envoy_cluster_grpc__success`
+* `envoy_cluster_grpc__total`
+* `envoy_cluster_grpc_upstream_` - **only when `upstream_stats: true`**
+
+Please note that `` will only be present if `all_methods` is set or the service and the method are present under `services`. If `all_methods` is false or the method is not on the list, the available metrics will be in the format `envoy_cluster_grpc_`.
+
+* `all_methods`: If set to true, emit stats for all service/method names.
+If set to false, emit stats for all service/message types to the same stats without including the service/method in the name.
+**This option is only safe if all clients are trusted. If this option is enabled with untrusted clients, the clients could cause unbounded growth in the number
+of stats in Envoy, using unbounded memory and potentially slowing down stats pipelines.**
+
+* `services`: If set, specifies an allow list of service/methods that will have individual stats emitted for them. Any call that does not match the allow list will be counted in a stat with no method specifier (generic metric).
+
+
+ If both all_methods and services are present, all_methods will be ignored.
+
+
+* `upstream_stats`: If true, the filter will gather a histogram for the request time of the upstream.
+
+---
+
+## Header behavior
+
+##### Header case
+
+* `proper_case: true` forces headers to have their "proper" case as shown in RFC7230.
+* `header_case_overrides` allows forcing certain headers to have specific casing.
+
+proper_case and header_case_overrides are mutually exclusive.
+
+RFC7230 specifies that HTTP header names are case-insensitive, but always shows and refers to headers as starting with a capital letter, continuing in lowercase, then repeating the single capital letter after each non-alpha character. This has become an established convention when working with HTTP:
+
+- `Host`, not `host` or `HOST`
+- `Content-Type`, not `content-type`, `Content-type`, or `cOnTeNt-TyPe`
+
+Internally, Envoy typically uses [all lowercase](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/header_casing) for header names. This is fully compliant with RFC7230, but some services and clients may require headers to follow the stricter casing rules implied by RFC7230 section headers: in that situation, setting `proper_case: true` will tell Envoy to force all headers to use the casing above.
+
+Alternately, it is also possible - although less common - for services or clients to require some other specific casing for specific headers. `header_case_overrides` specifies an array of header names: if a case-insensitive match for a header is found in the list, the matching header will be replaced with the one in the list. For example, the following configuration will force headers that match `X-MY-Header` and `X-EXPERIMENTAL` to use that exact casing, regardless of the original case used in flight:
+
+```yaml
+header_case_overrides:
+- X-MY-Header
+- X-EXPERIMENTAL
+```
+
+If the upstream service responds with `x-my-header: 1`, $productName$ will return `X-MY-Header: 1` to the client. Similarly, if the client includes `x-ExperiMENTAL: yes` in its request, the request to the upstream service will include `X-EXPERIMENTAL: yes`. Other headers will not be altered; $productName$ will use its default lowercase header.
+
+Please see the [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto.html#config-core-v3-http1protocoloptions-headerkeyformat) for more information. Note that in general, we recommend updating clients and services rather than relying on `header_case_overrides`.
+
+##### Linkerd interoperability
+
+* `add_linkerd_headers: true` will force $productName$ to include the `l5d-dst-override` header for Linkerd.
+
+When using older Linkerd installations, requests going to an upstream service may need to include the `l5d-dst-override` header to ensure that Linkerd will route them correctly. Setting `add_linkerd_headers` does this automatically. See the [Mapping](../../using/mappings#linkerd-interoperability-add_linkerd_headers) documentation for more details.
+
+##### Max request headers size
+
+* `max_request_headers_kb` sets the maximum allowed request header size in kilobytes. If not set, the default is 60 KB.
+
+See [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto.html) for more information.
+
+##### Preserve external request ID
+
+* `preserve_external_request_id: true` will preserve any `X-Request-Id` header presented by the client. The default is `false`, in which case Envoy will always generate a new `X-Request-Id` value.
+
+##### Strip matching host port
+
+* `strip_matching_host_port: true` will tell $productName$ to strip any port number from the host/authority header before processing and routing the request if that port number matches the port number of Envoy's listener. The default is `false`, which will preserve any port number.
+
+In the default installation of $productName$ the public port is 443, which then maps internally to 8443, so this only works in custom installations where the public Service port and Envoy listener port match.
+
+A common reason to try using this property is if you are using gRPC with TLS and your client library appends the port to the Host header (i.e. `myurl.com:443`). We have an alternative solution in our [gRPC guide](../../../../../emissary/pre-release/howtos/grpc#working-with-host-headers-that-include-the-port) that uses a [Lua script](#lua-scripts) to remove all ports from every Host header for that use case.
+
+---
+
+## Miscellaneous
+
+
+##### Envoy's admin port
+
+* `admin_port` specifies the port where $productName$'s Envoy will listen for low-level admin requests. The default is 8001; it should almost never need changing.
+
+##### Lua scripts
+
+* `lua_scripts` allows defining a custom Lua script to run on every request.
+
+This is useful for simple use cases that mutate requests or responses, for example to add a custom header:
+
+```yaml
+lua_scripts: |
+ function envoy_on_response(response_handle)
+ response_handle:headers():add("Lua-Scripts-Enabled", "Processed")
+ end
+```
+
+For more details on the Lua API, see the [Envoy Lua filter documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter.html).
+
+Some caveats around the embedded scripts:
+
+* They run in-process, so any bugs in your Lua script can break every request.
+* They're run on every request/response to every URL.
+* They're inlined in the $productName$ YAML; as such, we do not recommend using Lua scripts for long, complex logic.
+
+If you need more flexible and configurable options, $AESproductName$ supports a [pluggable Filter system](../../using/filters/).
+
+##### Merge slashes
+
+* `merge_slashes: true` will cause $productName$ to merge adjacent slashes in incoming paths when doing route matching and request filtering: for example, a request for `//foo///bar` would be matched to a `Mapping` with prefix `/foo/bar`.
+
+##### Modify Default Buffer Size
+
+By default, the Envoy that ships with $productName$ uses a defailt of 1MiB soft limit for an upstream service's read and write buffer limits. This setting allows you to configure that buffer limit. See the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto.html?highlight=per_connection_buffer_limit_bytes) for more information.
+
+```yaml
+buffer_limit_bytes: 5242880 # Sets the default buffer limit to 5 MiB
+```
+
+##### Use $productName$ namespace for service resolution
+
+* `use_ambassador_namespace_for_service_resolution: true` tells $productName$ to assume that unqualified services are in the same namespace as $productName$
+
+By default, when $productName$ sees a service name without a namespace, it assumes that the namespace is the same as the resource referring to the service. For example, for this `Mapping`:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-1
+ namespace: foo
+spec:
+ hostname: "*"
+ prefix: /
+ service: upstream
+```
+
+$productName$ would look for a Service named `upstream` in namespace `foo`.
+
+However, if `use_ambassador_namespace_for_service_resolution` is `true`, this `Mapping` would look for a Service named `foo` in the namespace in which $productName$ is installed instead.
+
+---
+
+## Observability
+
+##### Diagnostics
+
+* `diagnostics` controls access to the diagnostics interface.
+
+By default, $productName$ creates a `Mapping` that allows access to the diagnostic interface at `/ambassador/v0/diag` from anywhere in the cluster. To disable the `Mapping` entirely, set `diagnostics.enabled` to `false`:
+
+
+```yaml
+diagnostics:
+ enabled: false
+```
+
+With diagnostics disabled, `/ambassador/v0/diag` will respond with 404; however, the service itself is still running, and `/ambassador/v0/diag/` is reachable from inside the $productName$ Pod at `https://localhost:8877`. You can use Kubernetes port forwarding to set up remote access to the diagnostics page temporarily:
+
+```
+kubectl port-forward -n ambassador deploy/ambassador 8877
+```
+
+Alternately, to leave the `Mapping` intact but restrict access to only the local Pod, set `diagnostics.allow_non_local` to `false`:
+
+```yaml
+diagnostics:
+ allow_non_local: true
+```
+
+See [Protecting Access to the Diagnostics Interface](../../../howtos/protecting-diag-access) for more information.
+
+---
+## Protocols
+
+##### Enable IPv4 and IPv6
+
+* `enable_ipv4` determines whether IPv4 DNS lookups are enabled. The default is `true`.
+* `enable_ipv6` determines whether IPv6 DNS lookups are enabled. The default is `false`.
+
+If both IPv4 and IPv6 are enabled, $productName$ will prefer IPv6. This can have strange effects if $productName$ receives `AAAA` records from a DNS lookup, but the underlying network of the pod doesn't actually support IPv6 traffic. For this reason, the default is IPv4 only.
+
+A [`Mapping`](../../using/mappings) can override both `enable_ipv4` and `enable_ipv6`, but if either is not stated explicitly in a `Mapping`, the values here are used. Most $productName$ installations will probably be able to avoid overriding these settings in Mappings.
+
+##### HTTP/1.0 support
+
+* `enable_http10: true` will enable handling incoming HTTP/1.0 and HTTP/0.9 requests. The default is `false`.
+
+---
+## Security
+
+##### Cross origin resource sharing (CORS)
+
+* `cors` sets the default CORS configuration for all mappings in the cluster. See the [CORS syntax](../../using/cors).
+
+For example:
+
+```yaml
+cors:
+ origins: http://foo.example,http://bar.example
+ methods: POST, GET, OPTIONS
+ ...
+```
+
+##### IP allow and deny
+
+* `ip_allow` and `ip_deny` define HTTP source IP address ranges to allow or deny.
+
+Only one of ip_allow and ip_deny may be specified.
+
+The default is to allow all traffic.
+
+If `ip_allow` is specified, any traffic not matching a range to allow will be denied. If `ip_deny` is specified, any traffic not matching a range to deny will be allowed. A list of ranges to allow and a separate list to deny may not both be specified.
+
+Both take a list of IP address ranges with a keyword specifying how to interpret the address, for example:
+
+```yaml
+ip_allow:
+- peer: 127.0.0.1
+- remote: 99.99.0.0/16
+```
+
+The keyword `peer` specifies that the match should happen using the IP address of the other end of the network connection carrying the request: `X-Forwarded-For` and the `PROXY` protocol are both ignored. Here, our example specifies that connections originating from the $productName$ pod itself should always be allowed.
+
+The keyword `remote` specifies that the match should happen using the IP address of the HTTP client, taking into account `X-Forwarded-For` and the `PROXY` protocol if they are allowed (if they are not allowed, or not present, the peer address will be used instead). This permits matches to behave correctly when, for example, $productName$ is behind a layer 7 load balancer. Here, our example specifies that HTTP clients from the IP address range `99.99.0.0` - `99.99.255.255` will be allowed.
+
+You may specify as many ranges for each kind of keyword as desired.
+
+##### Rejecting Client Requests With Escaped Slashes
+
+* `reject_requests_with_escaped_slashes: true` will tell $productName$ to reject requests containing escaped slashes.
+
+When set to `true`, $productName$ will reject any client requests that contain escaped slashes (`%2F`, `%2f`, `%5C`, or `%5c`) in their URI path by returning HTTP 400. By default, $productName$ will forward these requests unmodified.
+
+ - **Envoy and $productName$ behavior**
+
+ Internally, Envoy treats escaped and unescaped slashes distinctly for matching purposes. This means that an $productName$ mapping
+ for path `/httpbin/status` will not be matched by a request for `/httpbin%2fstatus`.
+
+ On the other hand, when using $productName$, escaped slashes will be treated like unescaped slashes when applying FilterPolicies. For example, a request to `/httpbin%2fstatus/200` will be matched against a FilterPolicy for `/httpbin/status/*`.
+
+ - **Security Concern Example**
+
+ With $productName$, this can become a security concern when combined with `bypass_auth` in the following scenario:
+
+ - Use a `Mapping` for path `/prefix` with `bypass_auth` set to true. The intention here is to apply no FilterPolicies under this prefix, by default.
+
+ - Use a `Mapping` for path `/prefix/secure/` without setting bypass_auth to true. The intention here is to selectively apply a FilterPolicy to this longer prefix.
+
+ - Have an upstream service that receives both `/prefix` and `/prefix/secure/` traffic (from the Mappings above), but the upstream service treats escaped and unescaped slashes equivalently.
+
+ In this scenario, when a client makes a request to `/prefix%2fsecure/secret.txt`, the underlying Envoy configuration will _not_ match the routing rule for `/prefix/secure/`, but will instead
+ match the routing rule for `/prefix` which has `bypass_auth` set to true. $productName$ FilterPolicies will _not_ be enforced in this case, and the upstream service will receive
+ a request that it treats equivalently to `/prefix/secure/secret.txt`, potentially leaking information that was assumed protected by an $productName$ FilterPolicy.
+
+ One way to avoid this particular scenario is to avoid using `bypass_auth` and instead use a FilterPolicy that applies no filters when no authorization behavior is desired.
+
+ The other way to avoid this scenario is to reject client requests with escaped slashes altogether to eliminate this class of path confusion security concerns. This is recommended when there is no known, legitimate reason to accept client requests that contain escaped slashes. This is especially true if it is not known whether upstream services will treat escaped and unescaped slashes equivalently.
+
+ This document is not intended to provide an exhaustive set of scenarios where path confusion can lead to security concerns. As part of good security practice it is recommended to audit end-to-end request flow and the behavior of each component’s escaped path handling to determine the best configuration for your use case.
+
+ - **Summary**
+
+ Envoy treats escaped and unescaped slashes _distinctly_ for matching purposes. Matching is the underlying behavior used by $productName$ Mappings.
+
+ $productName$ treats escaped and unescaped slashes _equivalently_ when selecting FilterPolicies. FilterPolicies are applied by $productName$ after Envoy has performed route matching.
+
+ Finally, whether upstream services treat escaped and unescaped slashes equivalently is entirely dependent on the upstream service, and therefore dependent on your use case. Configuration intended to implement security policies will require audit with respect to escaped slashes. By setting reject_requests_with_escaped_slashes, this class of security concern can largely be eliminated.
+
+##### Trust downstream client IP
+
+* `use_remote_address: false` tells $productName$ that it cannot trust the remote address of incoming connections, and must instead rely exclusively on the `X-Forwarded-For` header.
+
+When `true` (the default), $productName$ will append its own IP address to the `X-Forwarded-For` header so that upstream services of $productName$ can get the full set of IP addresses that have propagated a request. You may also need to set `externalTrafficPolicy: Local` on your `LoadBalancer` to propagate the original source IP address.
+
+See the [Envoy documentation on the `X-Forwarded-For header` ](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers) and the [Kubernetes documentation on preserving the client source IP](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip) for more details.
+
+##### `X-Forwarded-For` trusted hops
+
+* `xff_num_trusted_hops` sets how many L7 proxies ahead of $productName$ should be trusted.
+
+
+ This value is not dynamically configurable in Envoy. A restart is required changing the value of xff_num_trusted_hops for Envoy to respect the change.
+
+
+The value of `xff_num_trusted_hops` indicates the number of trusted proxies in front of $productName$. The default setting is 0 which tells Envoy to use the immediate downstream connection's IP address as the trusted client address. The trusted client address is used to populate the `remote_address` field used for rate limiting and can affect which IP address Envoy will set as `X-Envoy-External-Address`.
+
+`xff_num_trusted_hops` behavior is determined by the value of `use_remote_address` (which is true by default).
+
+* If `use_remote_address` is false and `xff_num_trusted_hops` is set to a value N that is greater than zero, the trusted client address is the (N+1)th address from the right end of XFF. (If the XFF contains fewer than N+1 addresses, Envoy falls back to using the immediate downstream connection’s source address as a trusted client address.)
+
+* If `use_remote_address` is true and `xff_num_trusted_hops` is set to a value N that is greater than zero, the trusted client address is the Nth address from the right end of XFF. (If the XFF contains fewer than N addresses, Envoy falls back to using the immediate downstream connection’s source address as a trusted client address.)
+
+Refer to [Envoy's documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html#x-forwarded-for) for some detailed examples of this interaction.
+
+---
+
+## Service health / timeouts
+
+##### Incoming connection idle timeout
+
+* `listener_idle_timeout_ms` sets the idle timeout for incoming connections.
+
+If set, this specifies the length of time (in milliseconds) that an incoming connection is allowed to be idle before being dropped. This can useful if you have proxies and/or firewalls in front of $productName$ and need to control how $productName$ initiates closing an idle TCP connection.
+
+If not set, the default is no timeout, meaning that incoming connections may remain idle forever.
+
+Please see the [Envoy documentation on HTTP protocol options](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#config-core-v3-httpprotocoloptions) for more information.
+
+##### Keepalive
+
+* `keepalive` sets the global TCP keepalive settings.
+
+$productName$ will use these settings for all `Mapping`s unless overridden in a `Mapping`'s configuration. Without `keepalive`, $productName$ follows the operating system defaults.
+
+For example, the following configuration:
+
+```yaml
+keepalive:
+ time: 2
+ interval: 2
+ probes: 100
+```
+
+would enable keepalives every two seconds (`interval`), starting after two seconds of idleness (`time`), with the connection being dropped if 100 keepalives are sent with no response (`probes`). For more information, see the [Envoy keepalive documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto.html#config-core-v3-tcpkeepalive).
+
+##### Upstream idle timeout
+
+* `cluster_idle_timeout_ms` sets the default idle timeout for upstream connections (by default, one hour).
+
+If set, this specifies the timeout (in milliseconds) after which an idle connection upstream is closed. The idle timeout can be completely disabled by setting `cluster_idle_timeout_ms: 0`, which risks idle upstream connections never getting closed.
+
+If not set, the default idle timeout is one hour.
+
+You can override this setting with [`idle_timeout_ms` on a `Mapping`](../../using/timeouts/).
+
+##### Upstream max lifetime
+
+* `cluster_max_connection_lifetime_ms` sets the default maximum lifetime of an upstream connection.
+
+If set, this specifies the maximum amount of time (in milliseconds) after which an upstream connection is drained and closed, regardless of whether it is idle or not. Connection recreation incurs additional overhead when processing requests. The overhead tends to be nominal for plaintext (HTTP) connections within the same cluster, but may be more significant for secure HTTPS connections or upstreams with high latency. For this reason, it is generally recommended to set this value to at least 10000 ms to minimize the amortized cost of connection recreation while providing a reasonable bound for connection lifetime.
+
+If not set (or set to zero), then upstream connections may remain open for arbitrarily long.
+
+You can override this setting with [`cluster_max_connection_lifetime_ms` on a `Mapping`](../../using/timeouts/).
+
+##### Request timeout
+
+* `cluster_request_timeout_ms` sets the default end-to-end timeout for a single request.
+
+If set, this specifies the default end-to-end timeout for every request.
+
+If not set, the default is three seconds.
+
+You can override this setting with [`timeout_ms` on a `Mapping`](../../using/timeouts/).
+
+##### Readiness and liveness probes
+
+* `readiness_probe` sets whether `/ambassador/v0/check_ready` is automatically mapped
+* `liveness_probe` sets whether `/ambassador/v0/check_alive` is automatically mapped
+
+By default, $productName$ creates `Mapping`s that support readiness and liveness checks at `/ambassador/v0/check_ready` and `/ambassador/v0/check_alive`. To disable the readiness `Mapping` entirely, set `readiness_probe.enabled` to `false`:
+
+
+```yaml
+readiness_probe:
+ enabled: false
+```
+
+Likewise, to disable the liveness `Mapping` entirely, set `liveness_probe.enabled` to `false`:
+
+
+```yaml
+liveness_probe:
+ enabled: false
+```
+
+A disabled probe endpoint will respond with 404; however, the service is still running, and will be accessible on localhost port 8877 from inside the $productName$ Pod.
+
+You can change these to route requests to some other service. For example, to have the readiness probe map to the `quote` application's health check:
+
+```yaml
+readiness_probe:
+ enabled: true
+ service: quote
+ rewrite: /backend/health
+```
+
+The liveness and readiness probes both support `prefix` and `rewrite`, with the same meanings as for [Mappings](../../using/mappings).
+
+##### Retry policy
+
+This lets you add resilience to your services in case of request failures by performing automatic retries.
+
+```yaml
+retry_policy:
+ retry_on: "5xx"
+```
+
+---
+
+## Traffic management
+
+##### Circuit breaking
+
+* `circuit_breakers` sets the global circuit breaking configuration defaults
+
+You can override the circuit breaker settings for individual `Mapping`s. By default, $productName$ does not configure any circuit breakers. For more information, see the [circuit breaking reference](../../using/circuit-breakers).
+
+##### Default label domain and labels
+
+* `default_labels` sets default domains and labels to apply to every request.
+
+For more on how to use the default labels, , see the [Rate Limit reference](../../using/rate-limits/#attaching-labels-to-requests).
+
+##### Default load balancer
+
+* `load_balancer` sets the default load balancing type and policy
+
+For example, to set the default load balancer to `least_request`:
+
+```yaml
+load_balancer:
+ policy: least_request
+```
+
+If not set, the default is to use round-robin load balancing. For more information, see the [load balancer reference](../load-balancer).
diff --git a/docs/edge-stack/latest/topics/running/debugging.md b/docs/edge-stack/latest/topics/running/debugging.md
new file mode 100644
index 000000000..8f62c7239
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/debugging.md
@@ -0,0 +1,203 @@
+# Debugging
+
+If you’re experiencing issues with the $productName$ and cannot diagnose the issue through the `/ambassador/v0/diag/` diagnostics endpoint, this document covers various approaches and advanced use cases for debugging $productName$ issues.
+
+We assume that you already have a running $productName$ installation in the following sections.
+
+## A Note on TLS
+
+[TLS] can appear intractable if you haven't set up [certificates] correctly. If you're
+having trouble with TLS, always [check the logs] of your $productName$ Pods and look for
+certificate errors.
+
+[tls]: ../tls
+[certificates]: ../tls#certificates-and-secrets
+[check the logs]: #review-logs
+
+## Check $productName$ status
+
+1. First, check the $productName$ Deployment with the following: `kubectl get -n $productNamespace$ deployments`
+
+ After a brief period, the terminal will print something similar to the following:
+
+ ```
+ $ kubectl get -n $productNamespace$ deployments
+ NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
+ $productDeploymentName$ 3 3 3 3 1m
+ $productDeploymentName$-apiext 3 3 3 3 1m
+ ```
+
+2. Check that the “desired” number of Pods matches the “current” and “available” number of Pods.
+
+3. If they are **not** equal, check the status of the associated Pods with the following command: `kubectl get pods -n $productNamespace$`.
+
+ The terminal should print something similar to the following:
+
+ ```
+ $ kubectl get pods -n $productNamespace$
+ NAME READY STATUS RESTARTS AGE
+ $productDeploymentName$-85c4cf67b-4pfj2 1/1 Running 0 1m
+ $productDeploymentName$-85c4cf67b-fqp9g 1/1 Running 0 1m
+ $productDeploymentName$-85c4cf67b-vg6p5 1/1 Running 0 1m
+ $productDeploymentName$-apiext-736f8497d-j34pf 1/1 Running 0 1m
+ $productDeploymentName$-apiext-736f8497d-9gfpq 1/1 Running 0 1m
+ $productDeploymentName$-apiext-736f8497d-p5wgx 1/1 Running 0 1m
+ ```
+
+ The actual names of the Pods will vary. All the Pods should indicate `Running`, and all should show 1/1 containers ready.
+
+4. If the Pods do not seem reasonable, use the following command for details about the history of the Deployment: `kubectl describe -n $productNamespace$ deployment $productDeploymentName$`
+
+ - Look for data in the “Replicas” field near the top of the output. For example:
+ `Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable`
+
+ - Look for data in the “Events” log field near the bottom of the output, which often displays data such as a fail image pull, RBAC issues, or a lack of cluster resources. For example:
+
+ ```
+ Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set $productDeploymentName$-85c4cf67b to 3
+ ```
+
+5. Additionally, use the following command to “describe” the individual Pods: `kubectl describe pods -n $productNamespace$ <$productDeploymentName$-pod-name>`
+
+ - Look for data in the “Status” field near the top of the output. For example, `Status: Running`
+
+ - Look for data in the “Events” field near the bottom of the output, as it will often show issues such as image pull failures, volume mount issues, and container crash loops. For example:
+ ```
+ Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal Scheduled 4m default-scheduler Successfully assigned $productDeploymentName$-85c4cf67b-4pfj2 to gke-ambassador-demo-default-pool-912378e5-dkxc
+ Normal SuccessfulMountVolume 4m kubelet, gke-ambassador-demo-default-pool-912378e5-dkxc MountVolume.SetUp succeeded for volume "$productDeploymentName$-token-tmk94"
+ Normal Pulling 4m kubelet, gke-ambassador-demo-default-pool-912378e5-dkxc pulling image "docker.io/datawire/ambassador:0.40.0"
+ Normal Pulled 4m kubelet, gke-$productDeploymentName$-demo-default-pool-912378e5-dkxc Successfully pulled image "docker.io/datawire/ambassador:0.40.0"
+ Normal Created 4m kubelet, gke-$productDeploymentName$-demo-default-pool-912378e5-dkxc Created container
+ Normal Started 4m kubelet, gke-$productDeploymentName$-demo-default-pool-912378e5-dkxc Started container
+ ```
+
+In both the Deployment Pod and the individual Pods, take the necessary action to address any discovered issues.
+
+
Review $productName$ logs
+
+$productName$ logging can provide information on anything that might be abnormal or malfunctioning. While there may be a large amount of data to sort through, look for key errors such as the $productName$ process restarting unexpectedly, or a malformed Envoy configuration.
+
+$productName$ has two major log mechanisms: $productName$ logging and Envoy logging. Both appear in the normal `kubectl logs` output, and both can have additional debug-level logging enabled.
+
+
+ Enabling debug-level logging can produce a lot of log output — enough to
+ potentially impact the performance of $productName$. We don't recommend running with debug
+ logging enabled as a matter of course; it's usually better to enable it only when needed,
+ then reset logging to normal once you're finished debugging.
+
+
+### $productName$ debug logging
+
+Much of $productName$'s logging is concerned with the business of noticing changes to
+Kubernetes resources that specify the $productName$ configuration, and generating new
+Envoy configuration in response to those changes. $productName$ also logs information
+about its built-in authentication, rate limiting, developer portal, ACME, etc. There
+are multiple environment variables controlling debug logging; which is required depends
+on which aspect of the system you want to debug:
+
+- Set `AES_LOG_LEVEL=debug` to debug the early boot sequence, $productName$'s interactions
+ with the Kubernetes cluster (finding changed resources, etc.), and the built-in services
+ (auth, rate limiting, etc.).
+- Set `AMBASSADOR_DEBUG=diagd` to debug the process of generating an Envoy configuration from
+ the input resources.
+
+### $productName$ Envoy logging
+
+Envoy logging is concerned with the actions Envoy is taking for incoming requests.
+Typically, Envoy will only output access logs, and certain errors, but enabling Envoy
+debug logging will show very verbose information about the actions Envoy is actually
+taking. It can be useful for understanding why connections are being closed, or whether
+an error status is coming from Envoy or from the upstream service.
+
+It is possible to enable Envoy logging at boot, but for the most part, it's safer to
+enable it at runtime, right before sending a request that is known to have problems.
+To enable Envoy debug logging, use `kubectl exec` to get a shell on the $productName$
+pod, then:
+
+ ```
+ curl -XPOST http://localhost:8001/logging?level=trace && \
+ sleep 10 && \
+ curl -XPOST http://localhost:8001/logging?level=warning
+ ```
+
+This will turn on Envoy debug logging for ten seconds, then turn it off again.
+
+#### Interpreting Response Codes
+
+Envoys default access log format includes the `%RESPONSE_FLAGS%` which provides additional information about the response or connection that can help with debugging issues.
+
+For example, if a log line includes `UAEX` then this indicates that an Edge Stack Filter has denied the request. This can occur because a user was not authenticated or because of an error. Therefore, this can indicate that further investigation of the logs is needed.
+
+See Envoy's documentation for a full list of the supported [%RESPONSE_FLAGS%](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators).
+
+### Viewing logs
+
+To view the logs from $productName$:
+
+1. Use the following command to target an individual $productName$ Pod: `kubectl get pods -n $productNamespace$`
+
+ The terminal will print something similar to the following:
+
+ ```
+ $ kubectl get pods -n $productNamespace$
+ NAME READY STATUS RESTARTS AGE
+ $productDeploymentName$-85c4cf67b-4pfj2 1/1 Running 0 3m
+ ```
+
+2. Then, run the following: `kubectl logs -n $productNamespace$ <$productDeploymentName$-pod-name>`
+
+The terminal will print something similar to the following:
+
+ ```
+ $ kubectl logs -n $productNamespace$ $productDeploymentName$-85c4cf67b-4pfj2
+ 2018-10-10 12:26:50 kubewatch 0.40.0 INFO: generating config with gencount 1 (0 changes)
+ /usr/lib/python3.6/site-packages/pkg_resources/__init__.py:1235: UserWarning: /ambassador is writable by group/others and vulnerable to attack when used with get_resource_filename. Consider a more secure location (set with .set_extraction_path or the PYTHON_EGG_CACHE environment variable).
+ warnings.warn(msg, UserWarning)
+ 2018-10-10 12:26:51 kubewatch 0.40.0 INFO: Scout reports {"latest_version": "0.40.0", "application": "ambassador", "notices": [], "cached": false, "timestamp": 1539606411.061929}
+
+ 2018-10-10 12:26:54 diagd 0.40.0 [P15TMainThread] INFO: thread count 3, listening on 0.0.0.0:8877
+ [2018-10-10 12:26:54 +0000] [15] [INFO] Starting gunicorn 19.8.1
+ [2018-10-10 12:26:54 +0000] [15] [INFO] Listening at: http://0.0.0.0:8877 (15)
+ [2018-10-10 12:26:54 +0000] [15] [INFO] Using worker: threads
+ [2018-10-10 12:26:54 +0000] [42] [INFO] Booting worker with pid: 42
+ 2018-10-10 12:26:54 diagd 0.40.0 [P42TMainThread] INFO: Starting periodic updates
+ [2018-10-10 12:27:01.977][21][info][main] source/server/drain_manager_impl.cc:63] shutting down parent after drain
+ ```
+
+Note that many deployments will have multiple logs, and the logs are independent for each Pod.
+
+## Examine Pod and container contents
+
+You can examine the contents of the $productName$ Pod for issues, such as if volume mounts are correct and TLS certificates are present in the required directory, to determine if the Pod has the latest $productName$ configuration, or if the generated Envoy configuration is correct or as expected. In these instructions, we will look for problems related to the Envoy configuration.
+
+1. To look into an $productName$ Pod, get a shell on the Pod using `kubectl exec`. For example,
+
+ ```
+ kubectl exec -it -n $productNamespace$ <$productDeploymentName$-pod-name> -- bash
+ ```
+
+2. Determine the latest configuration. If you haven't overridden the configuration directory, the latest configuration will be in `/ambassador/snapshots`. If you have overridden it, $productName$ saves configurations in `$AMBASSADOR_CONFIG_BASE_DIR/snapshots`.
+
+ In the snapshots directory:
+
+ - `snapshot.yaml` contains the full input configuration that $productName$ has found;
+ - `aconf.json` contains the $productName$ configuration extracted from the snapshot;
+ - `ir.json` contains the IR constructed from the $productName$ configuration; and
+ - `econf.json` contains the Envoy configuration generated from the IR.
+
+ In the snapshots directory, the current configuration will be stored in files with no digit suffix, and older configurations have increasing numbers. For example, `ir.json` is current, `ir-1.json` is the next oldest, then `ir-2.json`, etc.
+
+3. If something is wrong with `snapshot` or `aconf`, there is an issue with your configuration. If something is wrong with `ir` or `econf`, you should [open an issue on Github](https://github.com/emissary-ingress/emissary/issues/new/choose).
+
+4. The actual input provided to Envoy is split into `$AMBASSADOR_CONFIG_BASE_DIR/bootstrap-ads.json` and `$AMBASSADOR_CONFIG_BASE_DIR/envoy/envoy.json`.
+
+ - The `bootstrap-ads.json` file contains details about Envoy statistics, logging, authentication, etc.
+ - The `envoy.json` file contains information about request routing.
+ - You may generally find it simplest to just look at the `econf.json` files in the `snapshot`
+ directory, which includes both kinds of configuration.
diff --git a/docs/edge-stack/latest/topics/running/environment.md b/docs/edge-stack/latest/topics/running/environment.md
new file mode 100644
index 000000000..54a05c9bb
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/environment.md
@@ -0,0 +1,751 @@
+# $productName$ Environment variables
+
+Use the following variables for the environment of your $productName$ container:
+
+| Variable | Default value | Value type |
+|----------------------------------------------------------------------------------------------------------- |-----------------------------------------------------|-------------------------------------------------------------------------------|
+| [`AMBASSADOR_ID`](#ambassador_id) | `[ "default" ]` | List of strings |
+| [`AES_LOG_LEVEL`](#aes_log_level)
+| [`AES_DISABLE_LICENSE_USAGE_REPORTING`](#aes_disable_license_usage_reporting) | `false` | Any: `true`=true and unset is false |
+| [`AGENT_CONFIG_RESOURCE_NAME`](#agent_config_resource_name) | `ambassador-agent-cloud-token` | String |
+| [`AMBASSADOR_AMBEX_NO_RATELIMIT`](#ambassador_ambex_no_ratelimit) | `false` | Boolean: `true`=true, any other value=false |
+| [`AMBASSADOR_AMBEX_SNAPSHOT_COUNT`](#ambassador_ambex_snapshot_count) | `30` | Integer |
+| [`AMBASSADOR_CLUSTER_ID`](#ambassador_cluster_id) | Empty | String |
+| [`AMBASSADOR_CONFIG_BASE_DIR`](#ambassador_config_base_dir) | `/ambassador` | String |
+| [`AMBASSADOR_DISABLE_FEATURES`](#ambassador_disable_features) | Empty | Any |
+| [`AMBASSADOR_DRAIN_TIME`](#ambassador_drain_time) | `600` | Integer |
+| [`AMBASSADOR_ENVOY_API_VERSION`](#ambassador_envoy_api_version) | `V3` | String Enum; `V3` or `V2` |
+| [`AMBASSADOR_GRPC_METRICS_SINK`](#ambassador_grpc_metrics_sink) | Empty | String (address:port) |
+| [`AMBASSADOR_HEALTHCHECK_BIND_ADDRESS`](#ambassador_healthcheck_bind_address)| `0.0.0.0` | String |
+| [`AMBASSADOR_HEALTHCHECK_BIND_PORT`](#ambassador_healthcheck_bind_port)| `8877` | Integer |
+| [`AMBASSADOR_HEALTHCHECK_IP_FAMILY`](#ambassador_healthcheck_ip_family)| `ANY` | String Enum; `IPV4_ONLY` or `IPV6_ONLY`|
+| [`AMBASSADOR_ISTIO_SECRET_DIR`](#ambassador_istio_secret_dir) | `/etc/istio-certs` | String |
+| [`AMBASSADOR_JSON_LOGGING`](#ambassador_json_logging) | `false` | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_READY_PORT`](#ambassador_ready_port) | `8006` | Integer |
+| [`AMBASSADOR_READY_LOG`](#ambassador_ready_log) | `false` | Boolean; [Go `strconv.ParseBool`] |
+| [`AMBASSADOR_LABEL_SELECTOR`](#ambassador_label_selector) | Empty | String (label=value) |
+| [`AMBASSADOR_NAMESPACE`](#ambassador_namespace) | `default` ([^1]) | Kubernetes namespace |
+| [`AMBASSADOR_RECONFIG_MAX_DELAY`](#ambassador_reconfig_max_delay) | `1` | Integer |
+| [`AMBASSADOR_SINGLE_NAMESPACE`](#ambassador_single_namespace) | Empty | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_SNAPSHOT_COUNT`](#ambassador_snapshot_count) | `4` | Integer |
+| [`AMBASSADOR_VERIFY_SSL_FALSE`](#ambassador_verify_ssl_false) | `false` | Boolean; `true`=true, any other value=false |
+| [`DD_ENTITY_ID`](#dd_entity_id) | Empty | String |
+| [`DOGSTATSD`](#dogstatsd) | `false` | Boolean; Python `value.lower() == "true"` |
+| [`SCOUT_DISABLE`](#scout_disable) | `false` | Boolean; `false`=false, any other value=true |
+| [`STATSD_ENABLED`](#statsd_enabled) | `false` | Boolean; Python `value.lower() == "true"` |
+| [`STATSD_PORT`](#statsd_port) | `8125` | Integer |
+| [`STATSD_HOST`](#statsd_host) | `statsd-sink` | String |
+| [`STATSD_FLUSH_INTERVAL`](#statsd_flush_interval) | `1` | Integer |
+| [`_AMBASSADOR_ID`](#_ambassador_id) | Empty | String |
+| [`_AMBASSADOR_TLS_SECRET_NAME`](#_ambassador_tls_secret_name) | Empty | String |
+| [`_AMBASSADOR_TLS_SECRET_NAMESPACE`](#_ambassador_tls_secret_namespace) | Empty | String |
+| [`_CONSUL_HOST`](#_consul_host) | Empty | String |
+| [`_CONSUL_PORT`](#_consul_port) | Empty | Integer |
+| [`AMBASSADOR_DISABLE_SNAPSHOT_SERVER`](#ambassador_disable_snapshot_server) | `false` | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_ENVOY_BASE_ID`](#ambassador_envoy_base_id) | `0` | Integer | | `false` | Boolean; Python `value.lower() == "true"` |
+| [`AES_RATELIMIT_PREVIEW`](#aes_ratelimit_preview) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`AES_AUTH_TIMEOUT`](#aes_auth_timeout) | `4s` | Duration; [Go `time.ParseDuration`][]
+| [`REDIS_SOCKET_TYPE`](#redis_socket_type) | `tcp` | Go network such as `tcp` or `unix`; see [Go `net.Dial`][] |
+| [`REDIS_URL`](#redis_url) | None, must be set explicitly | Go network address; for TCP this is a `host:port` pair; see [Go `net.Dial`][] |
+| [`REDIS_TLS_ENABLED`](#redis_tls_enabled) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_TLS_INSECURE`](#redis_tls_insecure) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_USERNAME`](#redis_username) | Empty | Plain string |
+| [`REDIS_PASSWORD`](#redis_password) | Empty | Plain string |
+| [`REDIS_AUTH`](#redis_auth) | Empty | Requires AES_RATELIMIT_PREVIEW; Plain string |
+| [`REDIS_POOL_SIZE`](#redis_pool_size) | `10` | Integer |
+| [`REDIS_PING_INTERVAL`](#redis_ping_interval) | `10s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_TIMEOUT`](#redis_timeout) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_SURGE_LIMIT_INTERVAL`](#redis_surge_limit_interval) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_SURGE_LIMIT_AFTER`](#redis_surge_limit_after) | The value of `REDIS_POOL_SIZE` | Integer |
+| [`REDIS_SURGE_POOL_SIZE`](#redis_surge_pool_size) | `0` | Integer |
+| [`REDIS_SURGE_POOL_DRAIN_INTERVAL`](#redis_surge_pool_drain_interval) | `1m` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PIPELINE_WINDOW`](#redis_pipeline_window) | `0` | Requires AES_RATELIMIT_PREVIEW; Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PIPELINE_LIMIT`](#redis_pipeline_limit) | `0` | Requires AES_RATELIMIT_PREVIEW; Integer; [Go `strconv.ParseInt`][] |
+| [`REDIS_TYPE`](#redis_type) | `SINGLE` | Requires AES_RATELIMIT_PREVIEW; String; SINGLE, SENTINEL, or CLUSTER |
+| [`REDIS_PERSECOND`](#redis_persecond) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_PERSECOND_SOCKET_TYPE`](#redis_persecond_socket_type) | None, must be set explicitly (if `REDIS_PERSECOND`) | Go network such as `tcp` or `unix`; see [Go `net.Dial`][] |
+| [`REDIS_PERSECOND_URL`](#redis_persecond_url) | None, must be set explicitly (if `REDIS_PERSECOND`) | Go network address; for TCP this is a `host:port` pair; see [Go `net.Dial`][] |
+| [`REDIS_PERSECOND_TLS_ENABLED`](#redis_persecond_tls_enabled) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_PERSECOND_TLS_INSECURE`](#redis_persecond_tls_insecure) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`REDIS_PERSECOND_USERNAME`](#redis_persecond_username) | Empty | Plain string |
+| [`REDIS_PERSECOND_PASSWORD`](#redis_persecond_password) | Empty | Plain string |
+| [`REDIS_PERSECOND_AUTH`](#redis_persecond_auth) | Empty | Requires AES_RATELIMIT_PREVIEW; Plain string |
+| [`REDIS_PERSECOND_POOL_SIZE`](#redis_persecond_pool_size) | `10` | Integer |
+| [`REDIS_PERSECOND_PING_INTERVAL`](#redis_persecond_ping_interval) | `10s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_TIMEOUT`](#redis_persecond_timeout) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_SURGE_LIMIT_INTERVAL`](#redis_persecond_surge_limit_interval) | `0s` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_SURGE_LIMIT_AFTER`](#redis_persecond_surge_limit_after) | The value of `REDIS_PERSECOND_POOL_SIZE` | Integer |
+| [`REDIS_PERSECOND_SURGE_POOL_SIZE`](#redis_persecond_surge_pool_size) | `0` | Integer |
+| [`REDIS_PERSECOND_SURGE_POOL_DRAIN_INTERVAL`](#redis_persecond_surge_pool_drain_interval) | `1m` | Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_TYPE`](#redis_persecond_type) | `SINGLE` | Requires AES_RATELIMIT_PREVIEW; String; SINGLE, SENTINEL, or CLUSTER |
+| [`REDIS_PERSECOND_PIPELINE_WINDOW`](#redis_persecond_pipeline_window) | `0` | Requires AES_RATELIMIT_PREVIEW; Duration; [Go `time.ParseDuration`][] |
+| [`REDIS_PERSECOND_PIPELINE_LIMIT`](#redis_persecond_pipeline_limit) | `0` | Requires AES_RATELIMIT_PREVIEW; Integer |
+| [`EXPIRATION_JITTER_MAX_SECONDS`](#expiration_jitter_max_seconds) | `300` | Integer |
+| [`USE_STATSD`](#use_statsd) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`STATSD_HOST`](#statsd_host) | `localhost` | Hostname |
+| [`STATSD_PORT`](#statsd_port) | `8125` | Integer |
+| [`GOSTATS_FLUSH_INTERVAL_SECONDS`](#gostats_flush_interval_seconds) | `5` | Integer |
+| [`LOCAL_CACHE_SIZE_IN_BYTES`](#local_cache_size_in_bytes) | `0` | Requires AES_RATELIMIT_PREVIEW; Integer |
+| [`NEAR_LIMIT_RATIO`](#near_limit_ratio) | `0.8` | Requires AES_RATELIMIT_PREVIEW; Float; [Go `strconv.ParseFloat`][] || Developer Portal | `AMBASSADOR_URL` | `https://api.example.com` | URL |
+| [`DEVPORTAL_CONTENT_URL`](#devportal_content_url) | `https://github.com/datawire/devportal-content` | git-remote URL |
+| [`DEVPORTAL_CONTENT_DIR`](#devportal_content_dir) | `/` | Rooted Git directory |
+| [`DEVPORTAL_CONTENT_BRANCH`](#devportal_content_branch) | `master` | Git branch name |
+| [`DEVPORTAL_DOCS_BASE_PATH`](#devportal_docs_base_path) | `/doc/` | Git branch name |
+| [`POLL_EVERY_SECS`](#poll_every_secs) | `60` | Integer |
+| [`AES_ACME_LEADER_DISABLE`](#aes_acme_leader_disable) | `false` | Boolean; [Go `strconv.ParseBool`][] |
+| [`AES_REPORT_DIAGNOSTICS_TO_CLOUD`](#aes_report_diagnostics_to_cloud) | `true` | Boolean; [Go `strconv.ParseBool`][] |
+| [`AES_SNAPSHOT_URL`](#aes_snapshot_url) | `http://emissary-ingress-admin.default:8005/snapshot-external` | hostname |
+| [`ENV_AES_SECRET_NAME`](#env_aes_secret_name) | `ambassador-edge-stack` | String |
+| [`ENV_AES_SECRET_NAMESPACE`](#env_aes_secret_namespace) | $productName$'s Namespace | String |
+
+
+## Feature Flag Environment Variables
+
+| Variable | Default value | Value type |
+|----------------------------------------------------------------------------------------- |-----------------------------------------------------|-------------------------------------------------------------------------------|
+| [`AMBASSADOR_EDS_BYPASS`](#ambassador_eds_bypass) | `false` | Boolean; Python `value.lower() == "true"` |
+| [`AMBASSADOR_FORCE_SECRET_VALIDATION`](#ambassador_force_secret_validation) | `false` | Boolean: `true`=true, any other value=false |
+| [`AMBASSADOR_KNATIVE_SUPPORT`](#ambassador_knative_support) | `false` | Boolean; non-empty=true, empty=false |
+| [`AMBASSADOR_UPDATE_MAPPING_STATUS`](#ambassador_update_mapping_status) | `false` | Boolean; `true`=true, any other value=false |
+| [`ENVOY_CONCURRENCY`](#envoy_concurrency) | Empty | Integer |
+| [`DISABLE_STRICT_LABEL_SELECTORS`](#disable_strict_label_selectors) | `false` | Boolean: `true`=true, any other value=false |
+
+### `AMBASSADOR_ID`
+
+$productName$ supports running multiple installs in the same cluster without restricting a given instance of $productName$ to a single namespace.
+The resources that are visible to an installation can be limited with the `AMBASSADOR_ID` environment variable.
+
+[More information](../../running/running#ambassador_id)
+
+### `AES_LOG_LEVEL`
+
+Adjust the log level by setting the `AES_LOG_LEVEL` environment variable; from least verbose to most verbose, the valid values are `error`, `warn`/`warning`, `info`, `debug`, and `trace`. The default is `info`.
+Log level names are case-insensitive.
+
+[More information](../../running/running#log-levels-and-debugging)
+
+### `AES_DISABLE_LICENSE_USAGE_REPORTING`
+
+Usage data is collected and sent to Ambassador Labs servers on regular intervals. If you are using an in-cluster AirGapped license and wish to disable this, then setting `AES_DISABLE_LICENSE_USAGE_REPORTING` to true will prevent $productName$ from reporting license usage data. If your cluster is using a Cloud license then users are required to send this data and cannot disable it.
+
+[More license information](../../../topics/using/licenses)
+
+### `AGENT_CONFIG_RESOURCE_NAME`
+
+Allows overriding the default config_map/secret that is used for extracting the CloudToken for connecting with Ambassador cloud. It allows all
+components (and not only the Ambassador Agent) to authenticate requests to Ambassador Cloud.
+If unset it will just fallback to searching for a config map or secret with the name of `ambassador-agent-cloud-token`. Note: the secret will take precedence if both a secret and config map are set.
+
+### `AMBASSADOR_AMBEX_NO_RATELIMIT`
+
+Completely disables ratelimiting Envoy reconfiguration under memory pressure. This can help performance with the endpoint or Consul resolvers, but could make OOMkills more likely with large configurations.
+The default is `false`, meaning that the rate limiter is active.
+
+[More information](../../../topics/concepts/rate-limiting-at-the-edge/)
+
+### `AMBASSADOR_AMBEX_SNAPSHOT_COUNT`
+
+Envoy-configuration snapshots get saved (as `ambex-#.json`) in `/ambassador/snapshots`. The number of snapshots is controlled by the `AMBASSADOR_AMBEX_SNAPSHOT_COUNT` environment variable.
+Set it to 0 to disable.
+
+[More information](../../running/debugging#examine-pod-and-container-contents)
+
+### `AMBASSADOR_CLUSTER_ID`
+
+Each $productName$ installation generates a unique cluster ID based on the UID of its Kubernetes namespace and its $productName$ ID: the resulting cluster ID is a UUID which cannot be used
+to reveal the namespace name nor $productName$ ID itself. $productName$ needs RBAC permission to get namespaces for this purpose, as shown in the default YAML files provided by Datawire;
+if not granted this permission it will generate a UUID based only on the $productName$ ID. To disable cluster ID generation entirely, set the environment variable
+`AMBASSADOR_CLUSTER_ID` to a UUID that will be used for the cluster ID.
+
+[More information](../../running/running#ambassador-edge-stack-usage-telemetry-scout)
+
+### `AMBASSADOR_CONFIG_BASE_DIR`
+
+Controls where $productName$ will store snapshots. By default, the latest configuration will be in `/ambassador/snapshots`. If you have overridden it, $productName$ saves configurations in `$AMBASSADOR_CONFIG_BASE_DIR/snapshots`.
+
+[More information](../../running/debugging#examine-pod-and-container-contents)
+
+### `AMBASSADOR_DISABLE_FEATURES`
+
+To completely disable feature reporting, set the environment variable `AMBASSADOR_DISABLE_FEATURES` to any non-empty value.
+
+[More information](../../running/running/#ambassador-edge-stack-usage-telemetry-scout)
+
+### `AMBASSADOR_DRAIN_TIME`
+
+At each reconfiguration, $productName$ keeps around the old version of it's envoy config for the duration of the configured drain time.
+The `AMBASSADOR_DRAIN_TIME` variable controls how much of a grace period $productName$ provides active clients when reconfiguration happens.
+Its unit is seconds and it defaults to 600 (10 minutes). This can impact memory usage because $productName$ needs to keep around old versions of its configuration
+for the duration of the drain time.
+
+[More information](../../running/scaling#ambassador_drain_time)
+
+### `AMBASSADOR_ENVOY_API_VERSION`
+
+By default, $productName$ will configure Envoy using the [V3 Envoy API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/api).
+In $productName$ 2.0, you were able switch back to Envoy V2 by setting the `AMBASSADOR_ENVOY_API_VERSION` environment variable to "V2".
+$productName$ 3.0 has removed support for the V2 API and only the V3 API is used. While this variable cannot be set to another value in 3.0, it may
+be used when introducing new API versions that are not yet available in $productName$ such as V4.
+
+### `AMBASSADOR_GRPC_METRICS_SINK`
+
+Configures $productName$ (envoy) to send metrics to the Agent which are then relayed to the Cloud. If not set then we don’t configure envoy to send metrics to the agent. If set with a bad address:port then we log an error message. In either scenario, it just stops metrics from being sent to the Agent which has no negative effect on general routing or $productName$ uptime.
+
+### `AMBASSADOR_HEALTHCHECK_BIND_ADDRESS`
+
+Configures $productName$ to bind its health check server to the provided address. If not set $productName$ will bind to all addresses (`0.0.0.0`).
+
+### `AMBASSADOR_HEALTHCHECK_BIND_PORT`
+
+Configures $productName$ to bind its health check server to the provided port. If not set $productName$ will listen on the admin port(`8877`).
+
+### `AMBASSADOR_HEALTHCHECK_IP_FAMILY`
+
+Allows the IP Family used by health check server to be overriden. By default, the health check server will listen for both IPV4 and IPV6 addresses. In some clusters you may want to force `IPV4_ONLY` or `IPV6_ONLY`.
+
+### `AMBASSADOR_ISTIO_SECRET_DIR`
+
+$productName$ will read the mTLS certificates from `/etc/istio-certs` unless configured to use a different directory with the `AMBASSADOR_ISTIO_SECRET_DIR`
+environment variable and create a secret in that location named `istio-certs`.
+
+[More information](../../../howtos/istio#configure-an-mtls-tlscontext)
+
+### `AMBASSADOR_JSON_LOGGING`
+
+When `AMBASSADOR_JSON_LOGGING` is set to `true`, JSON format will be used for most of the control plane logs.
+Some (but few) logs from `gunicorn` and the Kubernetes `client-go` package will still be in text only format.
+
+[More information](../../running/running#log-format)
+
+### `AMBASSADOR_READY_PORT`
+
+A dedicated Listener is created for non-blocking readiness checks. By default, the Listener will listen on the loopback address
+and port `8006`. `8006` is part of the reserved ports dedicated to $productName$. If their is a conflict then setting
+`AMBASSADOR_READY_PORT` to a valid port will configure Envoy to Listen on that port.
+
+### `AMBASSADOR_READY_LOG`
+
+When `AMBASSADOR_READY_LOG` is set to `true`, the envoy `/ready` endpoint will be logged. It will honor format
+provided in the `Module` resource or default to the standard log line format.
+
+### `AMBASSADOR_LABEL_SELECTOR`
+
+Restricts $productName$'s configuration to only the labelled resources. For example, you could apply a `version-two: true` label
+to all resources that should be visible to $productName$, then set `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+Resources without the specified label will be ignored.
+
+### `AMBASSADOR_NAMESPACE`
+
+Controls namespace configuration for Amabssador.
+
+[More information](../../running/running#namespaces)
+
+### `AMBASSADOR_RECONFIG_MAX_DELAY`
+
+Controls up to how long Ambassador will wait to receive changes before doing an Envoy reconfiguration. The unit is
+in seconds and must be > 0.
+
+### `AMBASSADOR_SINGLE_NAMESPACE`
+
+When set, configures $productName$ to only work within a single namespace.
+
+[More information](../../running/running#namespaces)
+
+### `AMBASSADOR_SNAPSHOT_COUNT`
+
+The number of snapshots that $productName$ should save.
+
+### `AMBASSADOR_VERIFY_SSL_FALSE`
+
+By default, $productName$ will verify the TLS certificates provided by the Kubernetes API. In some situations, the cluster may be
+deployed with self-signed certificates. In this case, set `AMBASSADOR_VERIFY_SSL_FALSE` to `true` to disable verifying the TLS certificates.
+
+[More information](../../running/running#ambassador_verify_ssl_false)
+
+### `DD_ENTITY_ID`
+
+$productName$ supports setting the `dd.internal.entity_id` statitics tag using the `DD_ENTITY_ID` environment variable. If this value
+is set, statistics will be tagged with the value of the environment variable. Otherwise, this statistics tag will be omitted (the default).
+
+[More information](../../running/statistics/envoy-statsd#using-datadog-dogstatsd-as-the-statsd-sink)
+
+### `DOGSTATSD`
+
+If you are a user of the [Datadog](https://docs.datadoghq.com/) monitoring system, pulling in the Envoy statistics from $productName$ is very easy.
+Because the DogStatsD protocol is slightly different than the normal StatsD protocol, in addition to setting $productName$'s
+`STATSD_ENABLED=true` environment variable, you also need to set the`DOGSTATSD=true` environment variable.
+
+[More information](../../running/statistics/envoy-statsd#using-datadog-dogstatsd-as-the-statsd-sink)
+
+### `SCOUT_DISABLE`
+
+$productName$ integrates Scout, a service that periodically checks with Ambassador Labs servers to sends anonymized usage data and the $productName$ version. This information is important to us as we prioritize test coverage, bug fixes, and feature development. Note that the $productName$ will run regardless of the status of Scout.
+
+We do not recommend you disable Scout. This check can be disabled by setting
+the environment variable `SCOUT_DISABLE` to `1` in your $productName$ deployment.
+
+[More information](../../running/running#ambassador-edge-stack-usage-telemetry-scout)
+
+### `STATSD_ENABLED`
+
+If enabled, then $productName$ has Envoy expose metrics information via the ubiquitous and well-tested [StatsD](https://github.com/etsy/statsd)
+protocol. To enable this, you will simply need to set the environment variable `STATSD_ENABLED=true` in $productName$'s deployment YAML
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `STATSD_HOST`
+
+When this variable is set, $productName$ by default sends statistics to a Kubernetes service named `statsd-sink` on UDP port 8125 (the usual
+port of the StatsD protocol). You may instead tell $productName$ to send the statistics to a different StatsD server by setting the
+`STATSD_HOST` environment variable. This can be useful if you have an existing StatsD sink available in your cluster.
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `STATSD_PORT`
+
+Allows for configuring StatsD on a port other than the default (8125)
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `STATSD_FLUSH_INTERVAL`
+
+How often, in seconds, to submit statsd reports (if `STATSD_ENABLED`)
+
+[More information](../../running/statistics/envoy-statsd#envoy-statistics-with-statsd)
+
+### `_AMBASSADOR_ID`
+
+Used with the Ambassador Consul connector. Sets the Ambassador ID so multiple instances of this integration can run per-Cluster when there are multiple $productNamePlural$ (Required if `AMBASSADOR_ID` is set in your $productName$ `Deployment`
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_AMBASSADOR_TLS_SECRET_NAME`
+
+Used with the Ambassador Consul connector. Sets the name of the Kubernetes `v1.Secret` created by this program that contains the Consul-generated TLS certificate.
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_AMBASSADOR_TLS_SECRET_NAMESPACE`
+
+Used with the Ambassador Consul connector. Sets the namespace of the Kubernetes `v1.Secret` created by this program.
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_CONSUL_HOST`
+
+Used with the Ambassador Consul connector. Sets the IP or DNS name of the target Consul HTTP API server
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `_CONSUL_PORT`
+
+Used with the Ambassador Consul connector. Sets the port number of the target Consul HTTP API server.
+
+[More information](../../../howtos/consul#environment-variables)
+
+### `AMBASSADOR_DISABLE_SNAPSHOT_SERVER`
+
+Disables the built-in snapshot server
+
+### `AMBASSADOR_ENVOY_BASE_ID`
+
+Base ID of the Envoy process
+
+### `AES_RATELIMIT_PREVIEW`
+
+Enables support for redis clustering, local caching, and an upgraded redis client with improved scalability in
+preview mode.
+
+[More information](../aes-redis/#aes_ratelimit_preview)
+
+### `AES_AUTH_TIMEOUT`
+
+Configures the default timeout in the authentication extension.
+
+[More information](../aes-extensions/authentication/#timeout-variables)
+
+### `REDIS_SOCKET_TYPE`
+
+Redis currently support three different deployment methods. $productName$ can now support using a Redis deployed in any of these ways for rate
+limiting when `AES_RATELIMIT_PREVIEW=true`.
+
+[More information](../aes-redis#socket_type)
+
+### `REDIS_URL`
+
+The URL to dial to talk to Redis.
+
+This will be either a hostname:port pair or a comma separated list of
+hostname:port pairs depending on the [`REDIS_TYPE`](#redis_type) you are using.
+
+[More information](../aes-redis#url)
+
+### `REDIS_TLS_ENABLED`
+
+Specifies whether to use TLS when talking to Redis.
+
+[More information](../aes-redis#tls_enabled)
+
+### `REDIS_TLS_INSECURE`
+
+Specifies whether to skip certificate verification when using TLS to talk to Redis.
+
+[More information](../aes-redis#tls_insecure)
+
+### `REDIS_USERNAME`
+
+`REDIS_USERNAME` and `REDIS_PASSWORD` handle all Redis authentication that is separate from Rate Limit Preview so failing to set them when using `REDIS_AUTH` will result in Ambassador not being able to authenticate with Redis for all of its other functionality.
+
+[More information](../aes-redis#username)
+
+### `REDIS_PASSWORD`
+
+`REDIS_USERNAME` and `REDIS_PASSWORD` handle all Redis authentication that is separate from Rate Limit Preview so failing to set them when using `REDIS_AUTH` will result in Ambassador not being able to authenticate with Redis for all of its other functionality.
+
+[More information](../aes-redis#password)
+
+### `REDIS_AUTH`
+
+If you configure `REDIS_AUTH`, then `REDIS_USERNAME` cannot be changed from the value `default`, and
+`REDIS_PASSWORD` should contain the same value as `REDIS_AUTH`.
+
+[More information](../aes-redis#auth)
+
+### `REDIS_POOL_SIZE`
+
+The number of connections to keep around when idle. The total number of connections may go lower than this if there are errors.
+The total number of connections may go higher than this during a load surge.
+
+[More information](../aes-redis#pool_size)
+
+### `REDIS_PING_INTERVAL`
+
+The rate at which Ambassador will ping the idle connections in the normal pool
+(not extra connections created for a load surge).
+
+[More information](../aes-redis#ping_interval)
+
+### `REDIS_TIMEOUT`
+
+Sets 4 different timeouts:
+
+ 1. `(*net.Dialer).Timeout` for establishing connections
+ 2. `(*redis.Client).ReadTimeout` for reading a single complete response
+ 3. `(*redis.Client).WriteTimeout` for writing a single complete request
+ 4. The timeout when waiting for a connection to become available from the pool (not including the dial time, which is timed out separately)
+
+A value of "0" means "no timeout".
+
+[More information](../aes-redis#timeout)
+
+### `REDIS_SURGE_LIMIT_INTERVAL`
+
+During a load surge, if the pool is depleted, then Ambassador may create new
+connections to Redis in order to fulfill demand, at a maximum rate of one new
+connection per `REDIS_SURGE_LIMIT_INTERVAL`.
+
+[More information](../aes-redis#surge_limit_interval)
+
+### `REDIS_SURGE_LIMIT_AFTER`
+
+The number of connections that can be created _after_ the normal pool is
+depleted before `REDIS_SURGE_LIMIT_INTERVAL` kicks in.
+
+[More information](../aes-redis#surge_limit_after)
+
+### `REDIS_SURGE_POOL_SIZE`
+
+Normally during a surge, excess connections beyond `REDIS_POOL_SIZE` are
+closed immediately after they are done being used, instead of being returned
+to a pool.
+
+`REDIS_SURGE_POOL_SIZE` configures a "reserve" pool for excess connections
+created during a surge.
+
+[More information](../aes-redis#surge_pool_size)
+
+### `REDIS_SURGE_POOL_DRAIN_INTERVAL`
+
+How quickly to drain connections from the surge pool after a surge is over.
+
+[More information](../aes-redis#surge_pool_drain_interval)
+
+### `REDIS_PIPELINE_WINDOW`
+
+The duration after which internal pipelines will be flushed.
+
+[More information](../aes-redis#pipeline_window)
+
+### `REDIS_PIPELINE_LIMIT`
+
+The maximum number of commands that can be pipelined before flushing.
+
+[More information](../aes-redis#pipeline_limit)
+
+### `REDIS_TYPE`
+
+Redis currently support three different deployment methods. $productName$ can now support using a Redis deployed in any of these ways for rate
+limiting when `AES_RATELIMIT_PREVIEW=true`.
+
+[More information](../aes-redis#type)
+
+### `REDIS_PERSECOND`
+
+If true, a second Redis connection pool is created (to a potentially different Redis instance) that is only used for per-second
+RateLimits; this second connection pool is configured by the `REDIS_PERSECOND_*` variables rather than the usual `REDIS_*` variables.
+
+[More information](../aes-redis#redis_persecond)
+
+### `REDIS_PERSECOND_SOCKET_TYPE`
+
+Configures the [REDIS_SOCKET_TYPE](#redis_socket_type) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#socket_type)
+
+### `REDIS_PERSECOND_URL`
+
+Configures the [REDIS_URL](#redis_url) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#url)
+
+### `REDIS_PERSECOND_TLS_ENABLED`
+
+Configures [REDIS_TLS_ENABLED](#redis_tls_enabled) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#tls_enabled)
+
+### `REDIS_PERSECOND_TLS_INSECURE`
+
+Configures [REDIS_TLS_INSECURE](#redis_tls_insecure) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#tls_insecure)
+
+### `REDIS_PERSECOND_USERNAME`
+
+Configures the [REDIS_USERNAME](#redis_username) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#username)
+
+### `REDIS_PERSECOND_PASSWORD`
+
+Configures the [#REDIS_PASSWORD](#redis_password) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#password)
+
+### `REDIS_PERSECOND_AUTH`
+
+Configures [REDIS_AUTH](#redis_auth) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#auth)
+
+### `REDIS_PERSECOND_POOL_SIZE`
+
+Configures the [REDIS_POOL_SIZE](#redis_pool_size) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#pool_size)
+
+### `REDIS_PERSECOND_PING_INTERVAL`
+
+Configures the [REDIS_PING_INTERVAL](#redis_ping_interval) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#ping_interval)
+
+### `REDIS_PERSECOND_TIMEOUT`
+
+Configures the [REDIS_TIMEOUT](#redis_timeout) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#timeout)
+
+### `REDIS_PERSECOND_SURGE_LIMIT_INTERVAL`
+
+Configures the [REDIS_SURGE_LIMIT_INTERVAL](#redis_surge_limit_interval) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_limit_interval)
+
+### `REDIS_PERSECOND_SURGE_LIMIT_AFTER`
+
+Configures [REDIS_SURGE_LIMIT_AFTER](#redis_surge_limit_after) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_limit_after)
+
+### `REDIS_PERSECOND_SURGE_POOL_SIZE`
+
+Configures the [REDIS_SURGE_POOL_SIZE](#redis_surge_pool_size) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_pool_size)
+
+### `REDIS_PERSECOND_SURGE_POOL_DRAIN_INTERVAL`
+
+Configures the [REDIS_SURGE_POOL_DRAIN_INTERVAL](#redis_surge_pool_drain_interval) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#surge_pool_drain_interval)
+
+### `REDIS_PERSECOND_TYPE`
+
+Configures the [REDIS_TYPE](#redis_type) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#type)
+
+### `REDIS_PERSECOND_PIPELINE_WINDOW`
+
+Configures the [REDIS_PIPELINE_WINDOW](#redis_pipeline_window) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#pipeline_window)
+
+### `REDIS_PERSECOND_PIPELINE_LIMIT`
+
+Configures the [REDIS_PIPELING_LIMIT](#redis_pipeline_limit) for the second [REDIS_PERSECOND](#redis_persecond) connection pool.
+
+[More information](../aes-redis#pipeline_limit)
+
+### `EXPIRATION_JITTER_MAX_SECONDS`
+
+### `USE_STATSD`
+
+The `RateLimitService` reports to statsd, and attempts to do so by default (`USE_STATSD`, `STATSD_HOST`, `STATSD_PORT`, `GOSTATS_FLUSH_INTERVAL_SECONDS`).
+
+### `GOSTATS_FLUSH_INTERVAL_SECONDS`
+
+Configures the flush interval in seconds for the go statistics.
+
+### `LOCAL_CACHE_SIZE_IN_BYTES`
+
+Only available if `AES_RATELIMIT_PREVIEW: "true`. The AES rate limit extension can optionally cache over-the-limit keys so it does
+not need to read the redis cache again for requests with labels that are already over the limit.
+
+Setting `LOCAL_CACHE_SIZE_IN_BYTES` to a non-zero value with enable local caching.
+
+[More information](../aes-extensions/ratelimit#local_cache_size_in_bytes)
+
+### `NEAR_LIMIT_RATIO`
+
+Only available if `AES_RATELIMIT_PREVIEW: "true"`. Adjusts the ratio used by the `near_limit` statistic for tracking requests that
+are "near the limit". Defaults to `0.8` (80%) of the limit defined in the `RateLimit` rule.
+
+[More information](../aes-extensions/ratelimit#near_limit_ratio)
+
+### `DEVPORTAL_CONTENT_URL`
+
+Default URL to the repository hosting the content for the Portal
+
+[More information](../../using/dev-portal)
+
+### `DEVPORTAL_CONTENT_DIR`
+
+Default content subdirectory within the `DEVPORTAL_CONTENT_URL` the devportal content is located at (defaults to `/`)
+
+[More information](../../using/dev-portal)
+
+### `DEVPORTAL_CONTENT_BRANCH`
+
+Default content branch within the repo at `DEVPORTAL_CONTENT_URL` to use for the devportal content (defaults to `master`)
+
+[More information](../../using/dev-portal)
+
+### `DEVPORTAL_DOCS_BASE_PATH`
+
+Base path for each api doc (defaults to `/doc/`)
+
+[More information](../../using/dev-portal)
+
+### `POLL_EVERY_SECS`
+
+Interval for polling OpenAPI docs; default 60 seconds. Set to 0 to disable devportal polling.
+
+[More information](../../using/dev-portal)
+
+### `AES_ACME_LEADER_DISABLE`
+
+This prevents $productName$ from trying to manage ACME. When enabled, `Host` resources will be unable to use ACME to manage their tls secrets
+regardless of the config on the `Host` resource.
+
+### `AES_REPORT_DIAGNOSTICS_TO_CLOUD`
+
+By setting `AES_REPORT_DIAGNOSTICS_TO_CLOUD` to false, you can disable the feature where diagnostic information about your installation of $productName$
+will be sent to Ambassador cloud. If this variable is disabled, you will be unable to access cluster diagnostic information in the cloud.
+
+### `AES_SNAPSHOT_URL`
+
+Configures the default endpoint where config snapshots are stored and accessed.
+
+### `ENV_AES_SECRET_NAME`
+
+Use to override the name of the secret that $productName$ attempts to find licensing information in.
+
+### `ENV_AES_SECRET_NAMESPACE`
+
+Use to override the namespace of the secret that $productName$ attempts to find licensing information in.
+By default, $productName$ will look for the secret in the same namespace that $productName$ was installed in.
+
+### `AMBASSADOR_EDS_BYPASS`
+
+Bypasses EDS handling of endpoints and causes endpoints to be inserted to clusters manually. This can help resolve with `503 UH`
+caused by certification rotation relating to a delay between EDS + CDS.
+
+### `AMBASSADOR_FORCE_SECRET_VALIDATION`
+
+If you set the `AMBASSADOR_FORCE_SECRET_VALIDATION` environment variable, invalid Secrets will be rejected,
+and a `Host` or `TLSContext` resource attempting to use an invalid certificate will be disabled entirely.
+
+[More information](../../running/tls#certificates-and-secrets)
+
+### `AMBASSADOR_KNATIVE_SUPPORT`
+
+Enables support for knative
+
+### `AMBASSADOR_UPDATE_MAPPING_STATUS`
+
+If `AMBASSADOR_UPDATE_MAPPING_STATUS` is set to the string `true`, $productName$ will update the `status` of every `Mapping`
+CRD that it accepts for its configuration. This has no effect on the proper functioning of $productName$ itself, and can be a
+performance burden on installations with many `Mapping`s. It has no effect for `Mapping`s stored as annotations.
+
+The default is `false`. We recommend leaving `AMBASSADOR_UPDATE_MAPPING_STATUS` turned off unless required for external systems.
+
+[More information](../../running/running#ambassador_update_mapping_status)
+
+### `ENVOY_CONCURRENCY`
+
+Configures the optional [--concurrency](https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-concurrency) command line option when launching Envoy.
+This controls the number of worker threads used to serve requests and can be used to fine-tune system resource usage.
+
+### `DISABLE_STRICT_LABEL_SELECTORS`
+
+In $productName$ version `3.2`, a bug with how `Hosts` are associated with `Mappings` was fixed and with how `Listeners` are associated with `Hosts`. The `mappingSelector`\\`selector` fields in `Hosts` and `Listeners` were not
+properly being enforced in prior versions. If any single label from the selector was matched then the resources would be associated with each other instead
+of requiring all labels in the selector to be present. Additonally, if the `hostname` of a `Mapping` matched the `hostname` of a `Host` then they would be associated
+regardless of the configuration of `mappingSelector`\\`selector`.
+
+In version `3.2` this bug was fixed and resources that configure a selector will only be associated if **all** labels required by the selector are present.
+This brings the `mappingSelector` and `selector` fields in-line with how label selectors are used throughout Kubernetes. To avoid unexpected behavior after the upgrade,
+add all labels that configured in any `mappingSelector`\\`selector` to `Mappings` you want to associate with the `Host` or the `Hosts` you want to be associated with the `Listener`. You can opt-out of this fix and return to the old
+association behavior by setting the environment variable `DISABLE_STRICT_LABEL_SELECTORS` to `"true"` (default: `"false"`). A future version of
+$productName$ may remove the ability to opt-out of this bugfix.
+
+> **Note:** The `mappingSelector` field is only configurable on `v3alpha1` CRDs. In the `v2` CRDs the equivalent field is `selector`.
+either `selector` or `mappingSelector` may be configured in the `v3alpha1` CRDs, but `selector` has been deprecated in favour of `mappingSelector`.
+
+See The [Host documentation](../../running/host-crd#controlling-association-with-mappings) for more information about `Host` / `Mapping` association.
+
+## Port assignments
+
+$productName$ uses the following ports to listen for HTTP/HTTPS traffic automatically via TCP:
+
+| Port | Process | Function |
+|------|----------|---------------------------------------------------------|
+| 8001 | envoy | Internal stats, logging, etc.; not exposed outside pod |
+| 8002 | watt | Internal watt snapshot access; not exposed outside pod |
+| 8003 | ambex | Internal ambex snapshot access; not exposed outside pod |
+| 8004 | diagd | Internal `diagd` access; not exposed outside pod |
+| 8005 | snapshot | Exposes a scrubbed $productName$ snapshot outside of the pod |
+| 8080 | envoy | Default HTTP service port |
+| 8443 | envoy | Default HTTPS service port |
+| 8877 | diagd | Direct access to diagnostics UI; provided by `busyambassador entrypoint` |
+
+[^1]: This may change in a future release to reflect the Pods's
+ namespace if deployed to a namespace other than `default`.
+ https://github.com/emissary-ingress/emissary/issues/1583
+
+[Go `net.Dial`]: https://golang.org/pkg/net/#Dial
+[Go `strconv.ParseBool`]: https://golang.org/pkg/strconv/#ParseBool
+[Go `time.ParseDuration`]: https://pkg.go.dev/time#ParseDuration
+[Redis 6 ACL]: https://redis.io/topics/acl
diff --git a/docs/edge-stack/latest/topics/running/host-crd.md b/docs/edge-stack/latest/topics/running/host-crd.md
new file mode 100644
index 000000000..082a5f9d1
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/host-crd.md
@@ -0,0 +1,329 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Host** CRD
+
+The custom `Host` resource defines how $productName$ will be
+visible to the outside world. It collects all the following information in a
+single configuration resource:
+
+- The hostname by which $productName$ will be reachable
+- How $productName$ should handle TLS certificates
+- How $productName$ should handle secure and insecure requests
+- Which `Mappings` should be associated with this `Host`
+
+
+ Remember that Listener resources are required for a functioning
+ $productName$ installation!
+ Learn more about Listener.
+
+
+
+ Remember than $productName$ does not make sure that a wildcard Host exists! If the
+ wildcard behavior is needed, a Host with a hostname of "*"
+ must be defined by the user.
+
+
+A minimal `Host` resource, using Let’s Encrypt to handle TLS, would be:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: minimal-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ email: julian@example.com
+```
+
+This `Host` tells $productName$ to expect to be reached at `host.example.com`,
+and to manage TLS certificates using Let’s Encrypt, registering as
+`julian@example.com`. Since it doesn’t specify otherwise, requests using
+cleartext will be automatically redirected to use HTTPS, and $productName$ will
+not search for any specific further configuration resources related to this
+`Host`.
+
+Remember that a Listener will also be required for this example to
+be functional. Many examples of setting up `Host` and `Listener` are available in the
+[Configuring $productName$ to Communicate](../../../howtos/configure-communications) document.
+
+## Setting the `hostname`
+
+The `hostname` element tells $productName$ which hostnames to expect. `hostname` is a DNS glob,
+so all of the following are valid:
+
+- `host.example.com`
+- `*.example.com`
+- `host.example.*`
+
+The following are _not_ valid:
+
+- `host.*.com` -- Envoy supports only prefix and suffix globs
+- `*host.example.com` -- the wildcard must be its own element in the DNS name
+
+In all cases, the `hostname` is used to match the `:authority` header for HTTP routing.
+When TLS termination is active, the `hostname` is also used for SNI matching.
+
+## Controlling Association with `Mapping`s
+
+A `Mapping` will not be associated with a `Host` unless at least one of the following is true:
+
+- The `Mapping` specifies a `hostname` attribute that matches the `Host` in question.
+- The `Host` specifies a `mappingSelector` that matches the `Mapping`'s Kubernetes `label`s.
+
+> **Note:** The `mappingSelector` field is only configurable on `v3alpha1` CRDs. In the `v2` CRDs the equivalent field is `selector`.
+either `selector` or `mappingSelector` may be configured in the `v3alpha1` CRDs, but `selector` has been deprecated in favour of `mappingSelector`.
+
+If neither of the above is true, the `Mapping` will not be associated with the `Host` in
+question. This is intended to help manage memory consumption with large numbers of `Host`s and large
+numbers of `Mapping`s.
+
+If the `Host` specifies `mappingSelector` _and_ the `Mapping` specifies `hostname`, both must match
+for the association to happen.
+
+The `mappingSelector` is a Kubernetes [label selector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#labelselector-v1-meta). For a `Mapping` to be associated with a `Host` that uses `mappingSelector`, then **all** labels
+required by the `mappingSelector` must be present on the `Mapping` in order for it to be associated with the `Host`.
+A `Mapping` may have additional labels other than those required by the `mappingSelector` so long as the required labels are present.
+
+**in 2.0, only `matchLabels` is supported**, for example:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: minimal-host
+spec:
+ hostname: host.example.com
+ mappingSelector:
+ matchLabels:
+ examplehost: host
+```
+
+The above `Host` will associate with these `Mapping`s:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-label-match
+ labels:
+ examplehost: host # This matches the Host's mappingSelector.
+spec:
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-hostname-match
+spec:
+ hostname: host.example.com # This is an exact match of the Host's hostname.
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-hostname-glob-match
+spec:
+ hostname: '*.example.com' # This glob matches the Host's hostname too.
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: mapping-with-both-matches
+ labels:
+ examplehost: host # This matches the Host's mappingSelector.
+spec:
+ hostname: '*.example.com' # This glob matches the Host's hostname.
+ prefix: /httpbin/
+ service: http://httpbin.org
+```
+
+It will _not_ associate with any of these:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: skip-mapping-wrong-label
+ labels:
+ examplehost: staging # This doesn't match the Host's mappingSelector.
+spec:
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: skip-mapping-wrong-hostname
+spec:
+ hosname: 'bad.example.com' # This doesn't match the Host's hostname.
+ prefix: /httpbin/
+ service: http://httpbin.org
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: skip-mapping-still-wrong
+ labels:
+ examplehost: staging # This doesn't match the Host's mappingSelector,
+spec: # and if the Host specifies mappingSelector AND the
+ hostname: host.example.com # Mapping specifies hostname, BOTH must match. So
+ prefix: /httpbin/ # the matching hostname isn't good enough.
+ service: http://httpbin.org
+```
+
+Future versions of $productName$ will support `matchExpressions` as well.
+
+> **Note:** In $productName$ version `3.2`, a bug with how `Hosts` are associated with `Mappings` was fixed. The `mappingSelector` field in `Hosts` was not
+properly being enforced in prior versions. If any single label from the selector was matched then the `Host` would be associated with the `Mapping` instead
+of requiring all labels in the selector to be present. Additonally, if the `hostname` of the `Mapping` matched the `hostname` of the `Host` then they would be associated
+regardless of the configuration of `mappingSelector`.
+In version `3.2` this bug was fixed and a `Host` will only be associated with a `Mapping` if **all** labels required by the selector are present.
+This brings the `mappingSelector` field in-line with how label selectors are used throughout Kubernetes. To avoid unexpected behavior after the upgrade,
+add all labels that `Hosts` have in their `mappingSelector` to `Mappings` you want to associate with the `Host`. You can opt-out of this fix and return to the old
+`Mapping`/`Host` association behavior by setting the environment variable `DISABLE_STRICT_LABEL_SELECTORS` to `"true"` (default: `"false"`). A future version of
+$productName$ may remove the ability to opt-out of this bugfix.
+
+## Secure and insecure requests
+
+A **secure** request arrives via HTTPS; an **insecure** request does not. By default, secure requests will be routed and insecure requests will be redirected (using an HTTP 301 response) to HTTPS. The behavior of insecure requests can be overridden using the `requestPolicy` element of a `Host`:
+
+```yaml
+requestPolicy:
+ insecure:
+ action: insecure-action
+ additionalPort: insecure-port
+```
+
+The `insecure-action` can be one of:
+
+- `Redirect` (the default): redirect to HTTPS
+- `Route`: go ahead and route as normal; this will allow handling HTTP requests normally
+- `Reject`: reject the request with a 400 response
+
+```yaml
+requestPolicy:
+ insecure:
+ additionalPort: -1 # This is how to disable the default redirection from 8080.
+```
+
+Some special cases to be aware of here:
+
+- **Case matters in the actions:** you must use e.g. `Reject`, not `reject`.
+- The `X-Forwarded-Proto` header is honored when determining whether a request is secure or insecure. For more information, see "Load Balancers, the `Host` Resource, and `X-Forwarded-Proto`" below.
+- ACME challenges with prefix `/.well-known/acme-challenge/` are always forced to be considered insecure, since they are not supposed to arrive over HTTPS.
+- $AESproductName$ provides native handling of ACME challenges. If you are using this support, $AESproductName$ will automatically arrange for insecure ACME challenges to be handled correctly. If you are handling ACME yourself - as you must when running $OSSproductName$ - you will need to supply appropriate `Host` resources and `Mapping`s to correctly direct ACME challenges to your ACME challenge handler.
+
+## TLS settings
+
+The `Host` is responsible for high-level TLS configuration in $productName$. There are
+several settings covering TLS:
+
+### ACME support
+
+$AESproductName$ comes with built in support for automatic certificate
+management using the [ACME protocol](https://tools.ietf.org/html/rfc8555).
+
+It does this by using the `hostname` of a `Host` to request a certificate from
+the `acmeProvider.authority` using the `HTTP-01` challenge. After requesting a
+certificate, $AESproductName$ will then manage the renewal process automatically.
+
+The `acmeProvider` element of the `Host` configures the Certificate Authority
+$AESproductName$ will request the certificate from and the email address that the CA
+will use to notify about any lifecycle events of the certificate.
+
+```yaml
+acmeProvider:
+ authority: url-to-provider
+ email: email-of-registrant
+```
+
+**Notes on ACME Support:**
+
+- If the authority is not supplied, the Let’s Encrypt production environment is assumed.
+
+- In general, `email-of-registrant` is mandatory when using ACME: it should be
+ a valid email address that will reach someone responsible for certificate
+ management.
+
+- ACME stores certificates in Kubernetes secrets. The name of the secret can be
+ set using the `tlsSecret` element:
+
+ ```yaml
+ acmeProvider:
+ email: user@example.com
+ tlsSecret:
+ name: tls-cert
+ ```
+
+ if not supplied, a name will be automatically generated from the `hostname` and `email`.
+
+- $AESproductName$ uses the [`HTTP-01` challenge
+ ](https://letsencrypt.org/docs/challenge-types/) for ACME support:
+ - Does not require permission to edit DNS records
+ - The `hostname` must be reachable from the internet so the CA can check
+ `POST` to an endpoint in $AESproductName$.
+ - Wildcard domains are not supported.
+
+### `tlsSecret` enables TLS termination
+
+`tlsSecret` specifies a Kubernetes `Secret` is **required** for any TLS termination to occur. If ACME is enabled,
+it will set `tlsSecret`: in all other cases, TLS termination will not occur if `tlsSecret` is not specified.
+
+The following `Host` will configure $productName$ to read a `Secret` named
+`tls-cert` for a certificate to use when terminating TLS.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ authority: none
+ tlsSecret:
+ name: tls-cert
+```
+
+### `tlsContext` links to a `TLSContext` for additional configuration
+
+`tlsContext` specifies a [`TLSContext`](#) to use for additional TLS information. Note that you **must** still
+define `tlsSecret` for TLS termination to happen. It is an error to supply both `tlsContext` and `tls`.
+
+See the [TLS discussion](../tls) for more details.
+
+### `tls` allows manually providing additional configuration
+
+`tls` allows specifying most of the things a `TLSContext` can, inline in the `Host`. Note that you **must** still
+define `tlsSecret` for TLS termination to happen. It is an error to supply both `tlsContext` and `tls`.
+
+See the [TLS discussion](../tls) for more details.
+
+## Load balancers, the `Host` resource, and `X-Forwarded-Proto`
+
+In a typical installation, $productName$ runs behind a load balancer. The
+configuration of the load balancer can affect how $productName$ sees requests
+arriving from the outside world, which can in turn can affect whether $productName$
+considers the request secure or insecure. As such:
+
+- **We recommend layer 4 load balancers** unless your workload includes
+ long-lived connections with multiple requests arriving over the same
+ connection. For example, a workload with many requests carried over a small
+ number of long-lived gRPC connections.
+- **$productName$ fully supports TLS termination at the load balancer** with a single exception, listed below.
+- If you are using a layer 7 load balancer, **it is critical that the system be configured correctly**:
+ - The load balancer must correctly handle `X-Forwarded-For` and `X-Forwarded-Proto`.
+ - The `l7Depth` element in the [`Listener` CRD](../../running/listener) must be set to the number of layer 7 load balancers the request passes through to reach $productName$ (in the typical case, where the client speaks to the load balancer, which then speaks to $productName$, you would set `l7Depth` to 1). If `l7Depth` remains at its default of 0, the system might route correctly, but upstream services will see the load balancer's IP address instead of the actual client's IP address.
+
+It's important to realize that Envoy manages the `X-Forwarded-Proto` header such that it **always** reflects the most trustworthy information Envoy has about whether the request arrived encrypted or unencrypted. If no `X-Forwarded-Proto` is received from downstream, **or if it is considered untrustworthy**, Envoy will supply an `X-Forwarded-Proto` that reflects the protocol used for the connection to Envoy itself. The `l7Depth` element is also used when determining trust for `X-Forwarded-For`, and it is therefore important to set it correctly. Its default of 0 should always be correct when $productName$ is behind only layer 4 load balancers; it should need to be changed **only** when layer 7 load balancers are involved.
+
+### CRD specification
+
+The `Host` CRD is formally described by its protobuf specification. Developers who need access to the specification can find it [here](https://github.com/emissary-ingress/emissary/blob/release/v2.0/api/getambassador.io/v2/Host.proto).
diff --git a/docs/edge-stack/latest/topics/running/tls/cleartext-redirection.md b/docs/edge-stack/latest/topics/running/tls/cleartext-redirection.md
new file mode 100644
index 000000000..74fc88eeb
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/tls/cleartext-redirection.md
@@ -0,0 +1,91 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Cleartext support
+
+While most modern web applications choose to encrypt all traffic, there remain
+cases where supporting cleartext communications is important. $productName$ supports
+both forcing [automatic redirection to HTTPS](#http-https-redirection) and
+[serving cleartext](#cleartext-routing) traffic on a `Host`.
+
+
+ If no Hosts are defined, $productName$ enables HTTP->HTTPS redirection. You will
+ need to explicitly create a Host to enable cleartext communication at all.
+
+
+
+ The Listener and
+ Host CRDs work together to manage HTTP and HTTPS routing.
+ This document is meant as a quick reference to the Host resource: for a more complete
+ treatment of handling cleartext and HTTPS, see Configuring $productName$ Communications.
+
+
+## Cleartext Routing
+
+To allow cleartext to be routed, set the `requestPolicy.insecure.action` of a `Host` to `Route`:
+
+```yaml
+requestPolicy:
+ insecure:
+ action: Redirect
+```
+
+This allows routing for either HTTP and HTTPS, or _only_ HTTP, depending on `tlsSecret` configuration:
+
+- If the `Host` does not specify a `tlsSecret`, it will only route HTTP, not terminating TLS at all.
+- If the `Host` does specify a `tlsSecret`, it will route both HTTP and HTTPS.
+
+
+ If no Hosts are defined, $productName$ enables HTTP->HTTPS redirection. You will
+ need to explicitly create a Host to enable cleartext communication at all.
+
+
+
+ The Listener and
+ Host CRDs work together to manage HTTP and HTTPS routing.
+ This document is meant as a quick reference to the Host resource: for a more complete
+ treatment of handling cleartext and HTTPS, see Configuring $productName$ Communications.
+
+
+## HTTP->HTTPS redirection
+
+Most websites that force HTTPS will also automatically redirect any
+requests that come into it over HTTP:
+
+```
+Client $productName$
+| |
+| http:///api |
+| --------------------------> |
+| |
+| 301: https:///api |
+| <-------------------------- |
+| |
+| https:///api |
+| --------------------------> |
+| |
+```
+
+In $productName$, this is configured by setting the `insecure.action` in a `Host` to `Redirect`.
+
+```yaml
+requestPolicy:
+ insecure:
+ action: Redirect
+```
+
+$productName$ determines which requests are secure and which are insecure using the
+`securityModel` of the [`Listener`] that accepts the request.
+
+[`Listener`]: ../../listener
+
+
+ If no Hosts are defined, $productName$ enables HTTP->HTTPS redirection. You will
+ need to explicitly create a Host to enable cleartext communication at all.
+
+
+
+ The Listener and
+ Host CRDs work together to manage HTTP and HTTPS routing.
+ This document is meant as a quick reference to the Host resource: for a more complete
+ treatment of handling cleartext and HTTPS, see Configuring $productName$ Communications.
+
diff --git a/docs/edge-stack/latest/topics/running/tls/index.md b/docs/edge-stack/latest/topics/running/tls/index.md
new file mode 100644
index 000000000..b60dcb9fd
--- /dev/null
+++ b/docs/edge-stack/latest/topics/running/tls/index.md
@@ -0,0 +1,520 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Transport Layer Security (TLS)
+
+$productName$'s robust TLS support exposes configuration options
+for different TLS use cases including:
+
+- [Simultaneously Routing HTTP and HTTPS](cleartext-redirection#cleartext-routing)
+- [HTTP -> HTTPS Redirection](cleartext-redirection#http-https-redirection)
+- [Mutual TLS](mtls)
+- [Server Name Indication (SNI)](sni)
+- [TLS Origination](origination)
+
+## Certificates and Secrets
+
+Properly-functioning TLS requires the use of [TLS certificates] to prove that the
+various systems communicating are who they say they are. At minimum, $productName$
+must have a server certificate that identifies it to clients; when [mTLS](./mtls)
+or [client certificate authentication] are in use, additional certificates are needed.
+
+You supply certificates to $productName$ in Kubernetes [TLS Secrets]. These Secrets
+_must_ contain valid X.509 certificates with valid PKCS1, PKCS8, or Elliptic Curve private
+keys. If a Secret does not contain a valid certificate, an error message will be logged, for
+example:
+
+```
+tls-broken-cert.default.1 2 errors:; 1. K8sSecret secret tls-broken-cert.default tls.key cannot be parsed as PKCS1 or PKCS8: asn1: syntax error: data truncated; 2. K8sSecret secret tls-broken-cert.default tls.crt cannot be parsed as x.509: x509: malformed certificate
+```
+
+If you set the `AMBASSADOR_FORCE_SECRET_VALIDATION` environment variable, the invalid
+Secret will be rejected, and a `Host` or `TLSContext` resource attempting to use an invalid
+certificate will be disabled entirely. **Note** that in $productName$ $version$, this
+includes disabling cleartext communication for such a `Host`.
+
+[tls certificates]: https://protonmail.com/blog/tls-ssl-certificate/
+[client certificate authentication]: ../../../howtos/client-cert-validation/
+[tls secrets]: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets
+
+## `Host`
+
+A `Host` represents a domain in $productName$ and defines how the domain manages TLS. For more information on the Host resource, see [The Host CRD reference documentation](../host-crd).
+
+**If no `Host`s are present**, $productName$ synthesizes a `Host` that
+terminates TLS using a self-signed TLS certificate, and redirects cleartext
+traffic to HTTPS. You will need to explictly define `Host`s to change this behavior
+(for example, to use a different certificate or to route cleartext).
+
+
+ The examples below do not define a requestPolicy; however, most real-world
+ usage of $productName$ will require defining the requestPolicy.
+
+ For more information, please refer to the Host documentation.
+
+
+### Automatic TLS with ACME
+
+With $AESproductName$, you can configure the `Host` to manage TLS by
+requesting a certificate from a Certificate Authority using the
+[ACME HTTP-01 challenge](https://letsencrypt.org/docs/challenge-types/).
+
+After you create a DNS record, configure $AESproductName$ to get a certificate from the default CA, [Let's Encrypt](https://letsencrypt.org), by providing a hostname and your email for the certificate:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ authority: https://acme-v02.api.letsencrypt.org/directory # Optional: The CA you want to get your certificate from. Defaults to Let's Encrypt
+ email: julian@example.com
+```
+
+$AESproductName$ will now request a certificate from the CA and store it in a Secret
+in the same namespace as the `Host`.
+
+**If you use ACME for multiple Hosts, add a wildcard Host too.**
+This is required to manage a known issue. This issue will be resolved in a future Ambassador Edge Stack release.
+
+### Bring your own certificate
+
+The `Host` can read a certificate from a Kubernetes Secret and use that certificate
+to terminate TLS on a domain.
+
+The following example shows the certificate contained in the Kubernetes Secret named
+`host-secret` configured to have $productName$ terminate TLS on the `host.example.com`
+domain:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ tlsSecret:
+ name: host-secret
+```
+
+By default, `tlsSecret` will only look for the named secret in the same namespace as the `Host`.
+In the above example, the secret `host-secret` will need to exist within the `default` namespace
+since that is the namespace of the `Host`.
+
+To reference a secret that is in a different namespace from the `Host`, the `namespace` field is required.
+The below example will configure the `Host` to use the `host-secret` secret from the `example` namespace.
+
+```yaml
+---
+apiVersion: getambassador.io/v2
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ acmeProvider:
+ authority: none
+ tlsSecret:
+ name: host-secret
+ namespace: example
+```
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+### Advanced TLS configuration with the `Host`
+
+You can specify TLS configuration directly in the `Host` via the `tls` field. This is the
+recommended method to do more advanced TLS configuration for a single `Host`.
+
+For example, the configuration to enforce a minimum TLS version on the `Host` looks as follows:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ tlsSecret:
+ name: min-secret
+ tls:
+ min_tls_version: v1.2
+```
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+The following fields are accepted in the `tls` field:
+
+```yaml
+tls:
+ cert_chain_file: # string
+ private_key_file: # string
+ ca_secret: # string
+ cacert_chain_file: # string
+ alpn_protocols: # string
+ cert_required: # bool
+ min_tls_version: # string
+ max_tls_version: # string
+ cipher_suites: # array of strings
+ ecdh_curves: # array of strings
+ sni: # string
+ crl_secret: # string
+```
+
+These fields have the same function as in the [`TLSContext`](#tlscontext) resource,
+as described below.
+
+### `Host` and `TLSContext`
+
+You can link a `Host` to a [`TLSContext`](#tlscontext) instead of defining `tls`
+settings in the `Host` itself. This is primarily useful for sharing settings between
+multiple `Host`s.
+
+#### Link a `TLSContext` to the `Host`
+
+
+ It is invalid to use both the tls setting and the tlsContext
+ setting on the same Host. The recommended setting is using the tls setting
+ unless you have multiple Hosts that need to share TLS configuration.
+
+
+To link a [`TLSContext`](#tlscontext) with a `Host`, create a [`TLSContext`](#tlscontext)
+with the desired configuration and link it to the `Host` by setting the `tlsContext.name`
+field in the `Host`. For example, to enforce a minimum TLS version on the `Host` above,
+create a `TLSContext` with any name with the following configuration:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: min-tls-context
+spec:
+ hosts:
+ - host.example.com
+ secret: min-secret
+ min_tls_version: v1.2
+```
+
+Next, link it to the `Host` via the `tlsContext` field as shown:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Host
+metadata:
+ name: example-host
+spec:
+ hostname: host.example.com
+ tlsSecret:
+ name: min-secret
+ tlsContext:
+ name: min-tls-context
+```
+
+
+
+ The `Host` and the `TLSContext` must name the same Kubernetes Secret; if not,
+ $productName$ will disable TLS for the `Host`.
+
+
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+
+
+ The `Host`'s `hostname` and the `TLSContext`'s `hosts` must have compatible settings. If
+ they do not, requests may not be accepted.
+
+
+
+See [`TLSContext`](#tlscontext) below to read more on the description of these fields.
+
+#### Create a `TLSContext` with the name `{{AMBASSADORHOST}}-context` (DEPRECATED)
+
+
+ This implicit TLSContext linkage is deprecated and will be removed
+ in a future version of $productName$; it is not recommended for new
+ configurations. Any other TLS configuration in the Host will override
+ this implict TLSContext link.
+
+
+The `Host` will implicitly link to the `TLSContext` when a `TLSContext` exists with the following:
+
+- the name `{{NAME_OF_AMBASSADORHOST}}-context`
+- `hosts` in the `TLSContext` set to the same value as `hostname` in the `Host`, and
+- `secret` in the `TLSContext` set to the same value as `tlsSecret` in the `Host`
+
+**As noted above, this implicit linking is deprecated.**
+
+For example, another way to enforce a minimum TLS version on the `Host` above would
+be to simply create the `TLSContext` with the name `example-host-context` and then not modify the `Host`:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: example-host-context
+spec:
+ hosts:
+ - host.example.com
+ secret: host-secret
+ min_tls_version: v1.2
+```
+
+
+
+ The `Host` and the `TLSContext` must name the same Kubernetes Secret; if not,
+ $productName$ will disable TLS for the `Host`.
+
+
+
+
+
+ The Kubernetes Secret named by tlsSecret must contain a valid TLS certificate.
+ If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and the Secret contains an invalid
+ certificate, $productName$ will reject the Secret and completely disable the
+ `Host`; see [**Certificates and Secrets**](#certificates-and-secrets) above.
+
+
+
+
+
+ The `Host`'s `hostname` and the `TLSContext`'s `hosts` must have compatible settings. If
+ they do not, requests may not be accepted.
+
+
+
+Full reference for all options available to the `TLSContext` can be found [below](#tlscontext).
+
+## TLSContext
+
+The `TLSContext` is used to configure advanced TLS options in $productName$.
+Remember, a `TLSContext` must always be paired with a `Host`.
+
+A full schema of the `TLSContext` can be found below with descriptions of the
+different configuration options.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: example-host-context
+spec:
+ # 'hosts' defines the hosts for which this TLSContext is relevant.
+ # It ties into SNI. A TLSContext without "hosts" is useful only for
+ # originating TLS.
+ # type: array of strings
+ #
+ # hosts: []
+
+ # 'sni' defines the SNI string to use on originated connections.
+ # type: string
+ #
+ # sni: None
+
+ # 'secret' defines a Kubernetes Secret that contains the TLS certificate we
+ # use for origination or termination. If not specified, $productName$ will look
+ # at the value of cert_chain_file and private_key_file.
+ # type: string
+ #
+ # secret: None
+
+ # 'ca_secret' defines a Kubernetes Secret that contains the TLS certificate we
+ # use for verifying incoming TLS client certificates.
+ # type: string
+ #
+ # ca_secret: None
+
+ # Tells $productName$ whether to interpret a "." in the secret name as a "." or
+ # a namespace identifier.
+ # type: boolean
+ #
+ # secret_namespacing: true
+
+ # 'cert_required' can be set to true to _require_ TLS client certificate
+ # authentication.
+ # type: boolean
+ #
+ # cert_required: false
+
+ # 'alpn_protocols' is used to enable the TLS ALPN protocol. It is required
+ # if you want to do GRPC over TLS; typically it will be set to "h2" for that
+ # case.
+ # type: string (comma-separated list)
+ #
+ # alpn_protocols: None
+
+ # 'min_tls_version' sets the minimum acceptable TLS version: v1.0, v1.1,
+ # v1.2, or v1.3. It defaults to v1.0.
+ # min_tls_version: v1.0
+
+ # 'max_tls_version' sets the maximum acceptable TLS version: v1.0, v1.1,
+ # v1.2, or v1.3. It defaults to v1.3.
+ # max_tls_version: v1.3
+
+ # Tells $productName$ to load TLS certificates from a file in its container.
+ # type: string
+ #
+ # cert_chain_file: None
+ # private_key_file: None
+ # cacert_chain_file: None
+```
+
+
+
+ `secret` and (if used) `ca_secret` must specify Kubernetes Secrets containing valid TLS
+ certificates. If `AMBASSADOR_FORCE_SECRET_VALIDATION` is set and either Secret contains
+ an invalid certificate, $productName$ will reject the Secret, which will also completely
+ disable any `Host` using the `TLSContext`; see [**Certificates and Secrets**](#certificates-and-secrets)
+ above.
+
+
+
+### ALPN protocols
+
+The `alpn_protocols` setting configures the TLS ALPN protocol. To use gRPC over
+TLS, set `alpn_protocols: h2`. If you need to support HTTP/2 upgrade from
+HTTP/1, set `alpn_protocols: h2,http/1.1` in the configuration.
+
+#### HTTP/2 support
+
+The `alpn_protocols` setting is also required for HTTP/2 support.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: tls
+spec:
+ secret: ambassador-certs
+ hosts: ['*']
+ alpn_protocols: h2[, http/1.1]
+```
+
+Without setting alpn_protocols as shown above, HTTP2 will not be available via
+negotiation and will have to be explicitly requested by the client.
+
+If you leave off http/1.1, only HTTP2 connections will be supported.
+
+### TLS parameters
+
+The `min_tls_version` setting configures the minimum TLS protocol version that
+$productName$ will use to establish a secure connection. When a client
+using a lower version attempts to connect to the server, the handshake will
+result in the following error: `tls: protocol version not supported`.
+
+The `max_tls_version` setting configures the maximum TLS protocol version that
+$productName$ will use to establish a secure connection. When a client
+using a higher version attempts to connect to the server, the handshake will
+result in the following error:
+`tls: server selected unsupported protocol version`.
+
+The `cipher_suites` setting configures the supported ciphers found below using the
+[configuration parameters for BoringSSL](https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#Cipher-suite-configuration) when negotiating a TLS 1.0-1.2 connection.
+This setting has no effect when negotiating a TLS 1.3 connection. When a client does not
+support a matching cipher a handshake error will result.
+
+The `ecdh_curves` setting configures the supported ECDH curves when negotiating
+a TLS connection. When a client does not support a matching ECDH a handshake
+error will result.
+
+```
+ - AES128-SHA
+ - AES256-SHA
+ - AES128-GCM-SHA256
+ - AES256-GCM-SHA384
+ - ECDHE-RSA-AES128-SHA
+ - ECDHE-RSA-AES256-SHA
+ - ECDHE-RSA-AES128-GCM-SHA256
+ - ECDHE-RSA-AES256-GCM-SHA384
+ - ECDHE-RSA-CHACHA20-POLY1305
+ - ECDHE-ECDSA-AES128-SHA
+ - ECDHE-ECDSA-AES256-SHA
+ - ECDHE-ECDSA-AES128-GCM-SHA256
+ - ECDHE-ECDSA-AES256-GCM-SHA384
+ - ECDHE-ECDSA-CHACHA20-POLY1305
+ - ECDHE-PSK-AES128-CBC-SHA
+ - ECDHE-PSK-AES256-CBC-SHA
+ - ECDHE-PSK-CHACHA20-POLY1305
+ - PSK-AES128-CBC-SHA
+ - PSK-AES256-CBC-SHA
+ - DES-CBC3-SHA
+```
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: tls
+spec:
+ hosts: ['*']
+ secret: ambassador-certs
+ min_tls_version: v1.0
+ max_tls_version: v1.3
+ cipher_suites:
+ - '[ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305]'
+ - '[ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305]'
+ ecdh_curves:
+ - X25519
+ - P-256
+```
+
+The `crl_secret` field allows you to reference a Kubernetes Secret that contains a certificate revocation list.
+If specified, $productName$ will verify that the presented peer certificate has not been revoked by this CRL even if they are otherwise valid. This provides a way to reject certificates before they expire or if they become compromised.
+The `crl_secret` field takes a PEM-formatted [Certificate Revocation List](https://en.wikipedia.org/wiki/Certificate_revocation_list) in a `crl.pem` entry.
+
+Note that if a CRL is provided for any certificate authority in a trust chain, a CRL must be provided for all certificate authorities in that chain. Failure to do so will result in verification failure for both revoked and unrevoked certificates from that chain.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: TLSContext
+metadata:
+ name: tls-crl
+spec:
+ hosts: ["*"]
+ secret: ambassador-certs
+ min_tls_version: v1.0
+ max_tls_version: v1.3
+ crl_secret: 'ambassador-crl'
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: ambassador-crl
+ namespace: ambassador
+type: Opaque
+data:
+ crl.pem: |
+ {BASE64 CRL CONTENTS}
+---
+```
diff --git a/docs/edge-stack/latest/topics/using/dev-portal.md b/docs/edge-stack/latest/topics/using/dev-portal.md
new file mode 100644
index 000000000..4b405b0a8
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/dev-portal.md
@@ -0,0 +1,425 @@
+> **Developer Portal API visualization is now available in Ambassador Cloud. These docs will remain as a historical reference for hosted Developer Portal installations. [Go to the quick start guide](/docs/cloud/latest/visualize-api/quick-start/).**
+
+# Developer Portal
+
+## Rendering API documentation
+
+The _Dev Portal_ uses the `Mapping` resource to automatically discover services known by
+the Ambassador Edge Stack.
+
+For each `Mapping`, the _Dev Portal_ will attempt to fetch an OpenAPI V3 document
+when a `docs` attribute is specified.
+
+### `docs` attribute in `Mapping`s
+
+This documentation endpoint is defined by the optional `docs` attribute in the `Mapping`.
+
+```yaml
+ docs:
+ path: "string" # optional; default is ""
+ url: "string" # optional; default is ""
+ ignored: bool # optional; default is false
+ display_name: "string" # optional; default is ""
+```
+
+where:
+
+* `path`: path for the OpenAPI V3 document.
+The Ambassador Edge Stack will append the value of `docs.path` to the `prefix`
+in the `Mapping` so it will be able to use Envoy's routing capabilities for
+fetching the documentation from the upstream service . You will need to update
+your microservice to return a Swagger or OAPI document at this URL.
+* `url`: absolute URL to an OpenAPI V3 document.
+* `ignored`: ignore this `Mapping` for documenting services. Note that the service
+will appear in the _Dev Portal_ anyway if another, non-ignored `Mapping` exists
+for the same service.
+* `display_name`: custom name to show for this service in the devportal.
+
+> Note:
+>
+> Previous versions of the _Dev Portal_ tried to obtain documentation automatically
+> from `/.ambassador-internal/openapi-docs` by default, while the current version
+> will not try to obtain documentation unless a `docs` attribute is specified.
+> Users should set `docs.path` to `/.ambassador-internal/openapi-docs` in their `Mapping`s
+> in order to keep the previous behavior.
+>
+>
+> The `docs` field of Mappings was not introduced until `Ambassador Edge Stack` version 1.9 because Ambassador was automatically searching for docs on `/.ambassador-internal/openapi-docs`
+> Make sure to update your CRDs with the following command if you are encountering problems after upgrading from an earlier version of Ambassador.
+```yaml
+ `kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml`
+```
+
+> If you are on an earlier version of Ambassador, either upgrade to a newer version, or make your documentation available on `/.ambassador-internal/openapi-docs`.
+
+Example:
+
+With the `Mapping`s below, the _Dev Portal_ would fetch OpenAPI documentation
+from `service-a:5000` at the path `/srv/openapi/` and from `httpbin` from an
+external URL. `service-b` would have no documentation.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-a
+spec:
+ prefix: /service-a/
+ rewrite: /srv/
+ service: service-a:5000
+ docs:
+ path: /openapi/ ## docs will be obtained from `/srv/openapi/`
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-b
+spec:
+ prefix: /service-b/
+ service: service-b ## no `docs` attribute, so service-b will not be documented
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: regular-httpbin
+spec:
+ hostname: "*"
+ host_rewrite: httpbin.org
+ prefix: /httpbin/
+ service: httpbin.org
+ docs:
+ url: https://httpbin.org/spec.json
+```
+
+> Notes on access to documentation `path`s:
+>
+> By default, all the `path`s where documentation has been found will **NOT** be publicly
+> exposed by the Ambassador Edge Stack. This is controlled by a special
+> `FilterPolicy` installed internally.
+
+> Limitations on Mappings with a `host` attribute
+>
+> The Dev Portal will ignore `Mapping`s that contain `host`s that cannot be
+> parsed as a valid hostname, or use a regular expression (when `host_regex: true`).
+
+### Publishing the documentation
+
+All rendered API documentation is published at the `/docs/` URL by default. Users can
+achieve a higher level of customization by creating a `DevPortal` resource.
+`DevPortal` resources allow the customization of:
+
+- _what_ documentation is published
+- _how_ it looks
+
+Users can create a `DevPortal` resource for specifying the default configuration for
+the _Dev Portal_, filtering `Mappings` and namespaces and specifying the content.
+
+> Note: when several `DevPortal` resources exist, the Dev Portal will pick a random
+> one and ignore the rest. A specific `DevPortal` can be used as the default configuration
+> by setting the `default` attribute to `true`. Future versions will
+> use other `DevPortals` for configuring alternative _views_ of the Dev Portal.
+
+`DevPortal` resources have the following syntax:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: "string"
+ namespace: "string"
+spec:
+ default: bool ## optional; default false
+ docs: ## optional; default is []
+ - service: "string" ## required
+ url: "string" ## required
+ content: ## optional
+ url: "string" ## optional; see below
+ branch: "string" ## optional; see below
+ dir: "string" ## optional; see below
+ selector: ## optional
+ matchNamespaces: ## optional; default is []
+ - "string"
+ matchLabels: ## optional; default is {}
+ "string": "string"
+ naming_scheme: "string" ## optional; supported values [ "namespace.name", "name.prefix" ]; default "namespace.name"
+ preserve_servers: bool ## optional; default false
+ search:
+ enabled: bool ## optional; default false
+ type: "string" ## optional; supported values ["title-only", "all-content"]; default "title-only"
+```
+
+where:
+
+* `default`: `true` when this is the default Dev Portal configuration.
+* `content`: see [section below](#styling-the-devportal).
+* `selector`: rules for filtering `Mapping`s:
+ * `matchNamespaces`: list of namespaces, used for filtering the `Mapping`s that
+ will be shown in the `DevPortal`. When multiple namespaces are provided, the `DevPortal`
+ will consider `Mapping`s in **any** of those namespaces.
+ * `matchLabels`: dictionary of labels, filtering the `Mapping`s that will
+ be shown in the `DevPortal`. When multiple labels are provided, the `DevPortal`
+ will only consider the `Mapping`s that match **all** the labels.
+* `docs`: static list of _service_/_documentation_ pairs that will be shown
+ in the _Dev Portal_. Only the documentation from this list will be shown in the _Dev Portal_
+ (unless additional docs are included with a `selector`).
+ * `service`: service name used for listing user-provided documentation.
+ * `url`: a full URL to a OpenAPI document for this service. This document will be
+ served _as it is_, with no extra processing from the _Dev Portal_ (besides replacing
+ the _hostname_).
+* `naming_scheme`: Configures how DevPortal docs are displayed and linked to in the UI.
+ * "namespace.name" will display the docs with the namespace and name of the mapping.
+ e.g. a Mapping named `quote` in namespace `default` will be displayed as `default.quote`
+ and its docs will have the relative path of `/default/quote`
+ * "name.prefix" will display the docs with the name and prefix of the mapping.
+ e.g. a Mapping named `quote` with a prefix `backend` will be displayed as `quote.backend`
+ and its docs will have the relative path of `/quote/backend`
+* `preserve_servers`: Configures the DevPortal to no longer dynamically build server definitions
+ for the "try it out" request builder by using the Edge Stack hostname. When set to `true`, the
+ DevPortal will instead display the server definitions from the `servers` section of the Open API
+ docs supplied to the DevPortal for the service.
+* `search`: as of Edge Stack 1.13.0, the DevPortal content is now searchable
+ * `enabled`: default `false``; set to true to enable search functionality.
+ * When `enabled=false`, the DevPortal search endpoint (`/[DEVPORTAL_PATH/api/search`) will return an empty response
+ * `type`: Configure the items fed into search
+ * `title-only` (default): only search over the names of DevPortal services and markdown pages
+ * `all-content`: Search over openapi spec content and markdown page content.
+
+Example:
+
+The scope of the default _Dev Portal_ can be restricted to
+`Mappings` with the `public-api: true` and `documented: true` labels by creating
+a `DevPortal` `ambassador` resource like this:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ content:
+ url: https://github.com/datawire/devportal-content-v2.git
+ selector:
+ matchLabels:
+ public-api: "true" ## labels for matching only some Mappings
+ documented: "true" ## (note that "true" must be quoted)
+```
+
+Example:
+
+The _Dev Portal_ can show a static list OpenAPI docs. In this example, a `eks.aws-demo`
+_service_ is shown with the documentation obtained from a URL. In addition,
+the _Dev Portal_ will show documentation for all the services discovered in the
+`aws-demo` namespace:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ docs:
+ - service: eks.aws-demo
+ url: https://api.swaggerhub.com/apis/kkrlogistics/amazon-elastic_kubernetes_service/2017-11-01/swagger.json
+ selector:
+ matchNamespaces:
+ - aws-demo ## matches all the services in the `aws-demo` namespace
+ ## (note that Mappings must contain a `docs` attribute)
+```
+
+> Note:
+>
+> The free and unlicensed versions of `Ambassador Edge Stack` only support documentation for five services in the `DevPortal`.
+> When you start publishing documentation for more services to your `DevPortal`, keep in mind that you will not see more than 5 OpenAPI documents even if you have more than 5 services properly configured to report their OpenAPI specifications.
+> For more information on extending the number of services in your `DevPortal` please contact sales via our [pricing information page](/editions/).
+
+
+## Styling the `DevPortal`
+
+The look and feel of a `DevPortal` can be fully customized for your particular
+organization by specifying a different `content`, customizing not only _what_
+is shown but _how_ it is shown, and giving the possibility to
+add some specific content on your API documentation (e.g., best practices,
+usage tips, etc.) depending on where it has been published.
+
+The default _Dev Portal_ content is loaded in order from:
+
+- the `ambassador` `DevPortal` resource.
+- the Git repo specified in the optional `DEVPORTAL_CONTENT_URL` environment variable.
+- the default repository at [GitHub](https://github.com/datawire/devportal-content-v2.git).
+
+To use your own styling, clone or copy the repository, create an `ambassador` `DevPortal`
+and update the `content` attribute to point to the repository. If you wish to use a
+private GitHub repository, create a [Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line)
+and include it in the `content` following the example below:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ content:
+ url: https://9cb034008ddfs819da268d9z13b7ecd26@github.com/datawire/private-devportal-repo.git
+ selector:
+ matchLabels:
+ public-api: true
+```
+
+The `content` can be have the following attributes:
+
+```yaml
+ content:
+ url: "string" ## optional; default is the default repo
+ branch: "string" ## optional; default is "master"
+ dir: "string" ## optional; default is "/"
+```
+
+where:
+
+* `url`: Git URL for the content
+* `branch`: the Git branch
+* `dir`: subdirectory in the Git repo
+
+#### Iterating on _Dev Portal_ styling and content
+
+**Local Development**
+
+Check out a local copy of your content repo and from within run the following docker image:
+
+```
+docker run -it --rm --volume $PWD:/content --entrypoint local-devportal --publish 1080:1080
+ docker.io/datawire/aes:$version$ /content
+```
+
+and open `http://localhost:1080` in your browser. Any changes made locally to
+devportal content will be reflected immediately on page refresh.
+
+> Note:
+>
+> The docker command above will only work for AES versions 1.13.0+.
+
+**Remote Ambassador**
+
+After committing and pushing changes to your devportal content repo changes to git, set your DevPortal to fetch from your branch:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ content:
+ url: $REPO_URL
+ branch: $DEVELOPMENT_BRANCH
+```
+
+Then you can force a reload of DevPortal content by hitting a refresh endpoint on your remote ambassador:
+
+```
+# first, get your ambassador service
+export AMBASSADOR_LB_ENDPOINT=$(kubectl -n ambassador get svc ambassador \
+ -o "go-template={{range .status.loadBalancer.ingress}}{{or .ip .hostname}}{{end}}")
+
+# Then refresh the DevPortal content
+curl -X POST -Lk ${AMBASSADOR_LB_ENDPOINT}/docs/api/refreshContent
+```
+
+> Note:
+>
+> The DevPortal does not share a cache between replicas, so the content refresh endpoint
+> will only refresh the content on a single replica. It is suggested that you use this
+> endpoint in a single replica Edge Stack setup.
+
+#### Customizing documentation names and paths
+
+The _Dev Portal_ displays the documentation's Mapping name and namespace by default,
+but you can override this behavior.
+
+To change the documentation naming scheme for the entire _Dev Portal_, you can set
+`naming_scheme` in the `DevPortal` resource:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: DevPortal
+metadata:
+ name: ambassador
+spec:
+ default: true
+ naming_scheme: "name.prefix"
+```
+
+With the above configuration, a mapping for `service-a`:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-a
+spec:
+ prefix: /path/
+ service: service-a:5000
+ docs:
+ path: /openapi/
+```
+
+Will be displayed in the _Dev Portal_ as `service-a.path`,
+and the API documentation will be accessed at `$AMBASSADOR_URL/docs/doc/service-a/path`.
+
+You can also override the display name of documentation on a per-mapping basis.
+Per-mapping overrides will take precedence over the `DevPortal` `naming_scheme`.
+
+A mapping for `service-b` with `display_name` set:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: service-b
+spec:
+ prefix: /otherpath/
+ service: service-b:5000
+ docs:
+ path: /openapi/
+ display_name: "Cat Service"
+```
+
+Will be displayed in the _Dev Portal_ as `Cat Service`, and the documentation will be
+accessed at `$AMBASSADOR_URL/docs/doc/Cat%20Service`.
+
+
+## Default configuration
+
+The _Dev Portal_ supports some default configuration in some environment variables
+(for backwards compatibility).
+
+### Environment variables
+
+The _Dev Portal_ can also obtain some default configuration from environment variables
+defined in the AES `Deployment`. This configuration method is considered deprecated and
+kept only for backwards compatibility: users should configure the default values with
+the `ambassador` `DevPortal`.
+
+| Setting | Description |
+| ------------------------ | ------------------------------------------------------------------------------ |
+| AMBASSADOR_URL | External URL of Ambassador Edge Stack; include the protocol (e.g., `https://`) |
+| POLL_EVERY_SECS | Interval for polling OpenAPI docs; default 60 seconds |
+| DEVPORTAL_CONTENT_URL | Default URL to the repository hosting the content for the Portal |
+| DEVPORTAL_CONTENT_DIR | Default content subdir (defaults to `/`) |
+| DEVPORTAL_CONTENT_BRANCH | Default content branch (defaults to `master`) |
+| DEVPORTAL_DOCS_BASE_PATH | Base path for api docs (defaults to `/doc/`) |
+
+## Visualize your API documentation in the cloud
+
+If you haven't already done so, you may want to [connect your cluster to Ambassador Cloud](../../../tutorials/getting-started). Connected clusters will automatically report your `Mapping`s' OpenAPI documents, allowing you to host and visualize all of your services API documentation on a shared, secure and authenticated platform.
diff --git a/docs/edge-stack/latest/topics/using/filters/apikeys.md b/docs/edge-stack/latest/topics/using/filters/apikeys.md
new file mode 100644
index 000000000..7cd9e3479
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/filters/apikeys.md
@@ -0,0 +1,125 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using The API Keys Filter
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied.
+
+
+
+See the [API Key Filter API reference][] for an overview of all the supported fields.
+
+## APIKey Filter Quickstart
+
+1. Come up with an API Key value to use. For this example, we're going to use the string `example-apikey-value`
+
+2. Convert the API Key value to [base64][].
+
+ You can do this however you prefer, such as with an online tool like [base64encode.org][] or with the terminal:
+
+ ```console
+ $ echo -n example-api-key-value | base64
+ ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+ ```
+
+3. Create an [APIKey Filter][] with the encoded API Key from above:
+
+ ```yaml
+ kubectl apply -f -<
+ If you want to create more APIKeys, you can continue to add them to your secret. The keys (key-1 in the example) used in the Secret do not matter, so you can name them whatever helps you keep track of the associated API Keys.
+
+
+4. Create a [FilterPolicy resource][] to use the `Filter` created above
+
+ ```yaml
+ kubectl apply -f -< GET /backend/ HTTP/1.1
+ > Accept: */*
+ >
+ < HTTP/1.1 403 Forbidden
+ < content-type: application/json
+ < server: envoy
+ <
+ {"message":"API key not found","requestId":"","statusCode":403}
+ ```
+
+
+ The request was denied because the header was not found, but it will also be denied if you send the correct header with an invalid API Key.
+
+
+6. Send a request with the APIKey header and value.
+
+ ```console
+ $ curl -ki http://$GATEWAY_HOST/backend/ -H "example-key-header: example-api-key-value"
+
+ > GET /backend/ HTTP/1.1
+ > Accept: */*
+ > example-key-header: example-api-key-value
+ >
+ < HTTP/1.1 200 OK
+ < content-type: application/json
+ < server: envoy
+ <
+ {
+ "server": "buoyant-raspberry-ju848o1i",
+ "quote": "A principal idea is omnipresent, much like candy.",
+ "time": "2023-08-04T03:40:45.594594388Z"
+ }
+ ```
+
+
+ Success! Your requests are now validated against an APIKey Filter and will be denied if they do not supply a valid API key!
+
+
+[API Key Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-apikey
+[APIKey Filter]: ../../../../custom-resources/getambassador/v3alpha1/filter-apikey
+[FilterPolicy resource]: ../../../../custom-resources/getambassador/v3alpha1/filterpolicy
+[base64encode.org]: https://www.base64encode.org/
+[base64]: https://en.wikipedia.org/wiki/Base64
diff --git a/docs/edge-stack/latest/topics/using/filters/external.md b/docs/edge-stack/latest/topics/using/filters/external.md
new file mode 100644
index 000000000..5e8e587e9
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/filters/external.md
@@ -0,0 +1,182 @@
+# Using The External Filter
+
+The `External` `Filter` calls out to an external service speaking the
+[`ext_authz` protocol](../../../running/services/ext-authz), providing
+a highly flexible interface to plug in your own authentication,
+authorization, and filtering logic.
+
+
+
+The `External` spec is identical to the [`AuthService`
+spec](../../../running/services/auth-service), with the following
+exceptions:
+
+* In an `AuthService`, the `tls` field must be a string referring to a
+ `TLSContext`. In an `External` `Filter`, it may only be a Boolean;
+ referring to a `TLSContext` is not supported.
+* In an `AuthService`, the default value of the `add_linkerd_headers`
+ field is based on the [`ambassador`
+ `Module`](../../../running/ambassador). In an `External` `Filter`,
+ the default value is always `false`.
+* `External` `Filters` lack the `stats_name`, and
+ `add_auth_headers` fields that `AuthServices` have.
+
+
+
+See the [External Filter API reference][] for an overview of all the supported fields.
+
+## Tracing Header Propagation
+
+If $productName$ is configured to use a `TraceService`, Envoy will send tracing information as gRPC Metadata. Add the trace headers to the `allowed_request_headers` field to propagate the trace headers when using an ExternalFilter configured with `proto:http`. For example, if using **Zipkin** with **B3 Propagation** headers you can configure your External Filter like this:
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "my-ext-filter"
+ namespace: "my-namespace"
+spec:
+ External:
+ auth_service: "https://example-auth:3000"
+ proto: http
+ path_prefix: /check_request
+ allowed_request_headers:
+ - X-B3-Parentspanid
+ - X-B3-Sampled
+ - X-B3-Spanid
+ - X-B3-Traceid
+ - X-Envoy-Expected-Rq-Timeout-Ms
+ - X-Envoy-Internal
+ - X-Request-Id
+```
+
+## Configuring TLS Settings
+
+When an `External Filter` has the `auth_service` field configured with a URL that starts with `https://` then $productName$
+will attempt to communicate with the `AuthService` over a TLS connection. The following configurations are supported:
+
+1. Verify server certificate with host CA Certificates - *default when `tls: true`*
+2. Verify server certificate with provided CA Certificate
+3. Mutual TLS between client and server
+
+Overall, these new configuration options enhance the security of the communications between $productName$ and your `External Filter` by providing a way to
+verify the server's certificate, allowing customization of the trust verification process, and enabling mutual TLS (mTLS) between $productName$ and the
+`External Filter` service. By employing these security measures, users can have greater confidence in the authenticity, integrity,
+and confidentiality of their filter's actions, especially if it interacts with any sensitive information.
+
+### Example - Verify Server with Custom CA Certificate
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "my-ext-filter"
+ namespace: "my-namespace"
+spec:
+ External:
+ auth_service: "https://example-auth:3000"
+ proto: grpc
+ tlsConfig:
+ caCertificate:
+ fromSecret:
+ name: ca-cert-secret
+ namespace: shared-certs
+```
+
+### Example - Mutual TLS (mTLS)
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "my-ext-filter"
+ namespace: "my-namespace"
+spec:
+ External:
+ auth_service: "https://example-auth:3000"
+ proto: grpc
+ tlsConfig:
+ caCertificate:
+ fromSecret:
+ name: ca-cert-secret
+ namespace: shared-certs
+ certificate:
+ fromSecret:
+ name: client-cert-secret
+```
+
+## Metrics
+
+As of $productName$ 3.4.0, the following metrics for External Filters are available via the [metrics endpoint](../../../running/statistics/8877-metrics)
+
+| Metric | Type | Description |
+| ------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ambassador_edge_stack_external_filter_allowed` | Counter | Number of requests that were allowed by Ambassador Edge Stack External Filters. Includes requests that are allowed by failure_mode_allow when unable to connect to the External Filter. |
+| `ambassador_edge_stack_external_filter_denied` | Counter | Number of requests that were denied by Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter or due to a Filter config error. |
+| `ambassador_edge_stack_external_filter_error` | Counter | Number of errors returned directly from Ambassador Edge Stack External Filters and errors from an inability to connect to the External Filter |
+| `ambassador_edge_stack_external_handler_error` | Counter | Number of errors caused by Ambassador Edge Stack encountering invalid Filter config or an error while parsing the config. \nThese errors will always result in a HTTP 500 response being returned to the client and do not count towards metrics that track response codes from external filters. |
+| `ambassador_edge_stack_external_filter_rq_class` | Counter (with labels) | Aggregated counter of response code classes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter. |
+| `ambassador_edge_stack_external_filter_rq_status` | Counter (with labels) | Counter of response codes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter. |
+
+An example of what the metrics may look like can be seen below
+
+```
+# HELP ambassador_edge_stack_external_filter_allowed Number of requests that were allowed by Ambassador Edge Stack External Filters. Includes requests that are allowed by failure_mode_allow when unable to connect to the External Filter.
+# TYPE ambassador_edge_stack_external_filter_allowed counter
+ambassador_edge_stack_external_filter_allowed 2
+
+# HELP ambassador_edge_stack_external_filter_denied Number of requests that were denied by Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter or due to a Filter config error.
+# TYPE ambassador_edge_stack_external_filter_denied counter
+ambassador_edge_stack_external_filter_denied 12
+
+# HELP ambassador_edge_stack_external_filter_error Number of errors returned directly from Ambassador Edge Stack External Filters and errors from an inability to connect to the External Filter
+# TYPE ambassador_edge_stack_external_filter_error counter
+ambassador_edge_stack_external_filter_error 2
+
+# HELP ambassador_edge_stack_external_filter_rq_class Aggregated counter of response code classes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter.
+# TYPE ambassador_edge_stack_external_filter_rq_class counter
+ambassador_edge_stack_external_filter_rq_class{class="2xx"} 2
+ambassador_edge_stack_external_filter_rq_class{class="4xx"} 5
+ambassador_edge_stack_external_filter_rq_class{class="5xx"} 7
+
+# HELP ambassador_edge_stack_external_filter_rq_status Counter of response codes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter.
+# TYPE ambassador_edge_stack_external_filter_rq_status counter
+ambassador_edge_stack_external_filter_rq_status{status="200"} 2
+ambassador_edge_stack_external_filter_rq_status{status="401"} 3
+ambassador_edge_stack_external_filter_rq_status{status="403"} 2
+ambassador_edge_stack_external_filter_rq_status{status="500"} 2
+ambassador_edge_stack_external_filter_rq_status{status="501"} 5
+
+# HELP ambassador_edge_stack_external_handler_error Number of errors caused by Ambassador Edge Stack encountering invalid Filter config or an error while parsing the config. \nThese errors will always result in a HTTP 500 response being returned to the client and do not count towards metrics that track response codes from external filters.
+# TYPE ambassador_edge_stack_external_handler_error counter
+ambassador_edge_stack_external_handler_error 0
+```
+
+## Transport Protocol Migration
+
+> **Note:** The following information is only applicable to External Filters using `proto: grpc`
+As of $productName$ version 2.3, the `v2` transport protocol is deprecated and any External Filters making use
+of it should migrate to `v3` before support for `v2` is removed in a future release.
+
+The following imports simply need to be updated to migrate an External Filter
+
+`v2` Imports:
+
+```go
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+```
+
+`v3` Imports:
+
+```go
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+```
+
+In the [datawire/sample-external-service repository](https://github.com/datawire/Sample-External-Service), you can find examples of an External Filter using both the
+`v2` transport protocol as well as `v3` along with deployment instructions for reference. The External Filter in this repo does not perform any authorization and is instead meant to serve as a reference for the operations that an External can make use of.
+
+[External Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-external
diff --git a/docs/edge-stack/latest/topics/using/filters/index.md b/docs/edge-stack/latest/topics/using/filters/index.md
new file mode 100644
index 000000000..cefae8b4b
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/filters/index.md
@@ -0,0 +1,109 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using Filters and FilterPolicies
+
+Filters are used to extend the Ambassador Edge Stack to modify or intercept a request before sending to your
+backend service. The most common use case for Filters is authentication, and Edge Stack includes a number
+of built-in filters for this purpose. Edge Stack also supports developing custom filters.
+
+## Filter types
+
+Edge Stack supports the following filter types:
+
+- [JWT][]: Validates JSON Web Tokens
+- [OAuth2][]: Performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+- [Plugin][]: Allows users to write custom Filters in Go that run as part of the Edge Stack container
+- [External][]: Allows users to call out to other services for request processing. This can include both custom services (in any language) or third party services.
+- [API Keys][]: Validates API Keys present in a custom HTTP header
+
+## Managing Filters
+
+Filters are created with the `Filter` resource type, which contains global arguments to that filter.
+Which Filter(s) to use for which HTTP requests is then configured in [FilterPolicy resources][],
+which may contain path-specific arguments to the filter.
+
+## Using a Filter in a FilterPolicy
+
+In the example below, the `param-filter` Filter Plugin is loaded and configured to run on requests to `/httpbin/`.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: param-filter # This is the name used in FilterPolicy
+ namespace: standalone
+spec:
+ Plugin:
+ name: param-filter # The plugin's `.so` file's base name
+
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: httpbin-policy
+spec:
+ rules:
+ # Don't apply any filters to requests for /httpbin/ip
+ - host: "*"
+ path: /httpbin/ip
+ filters: null
+ # Apply param-filter and auth0 to requests for /httpbin/
+ - host: "*"
+ path: /httpbin/*
+ filters:
+ - name: param-filter
+ - name: auth0
+ # Default to authorizing all requests with auth0
+ - host: "*"
+ path: "*"
+ filters:
+ - name: auth0
+```
+
+
+ Edge Stack will choose the first FilterPolicy rule that matches the incoming request. As in the above example, you must list your rules in the order of least to most generic.
+
+
+## FilterPolicies With Multiple Domains
+
+In this example, the `foo-keycloak` filter is used for requests to `foo.bar.com`, while the `example-auth0` filter is used for requests to `example.com`. This configuration is useful if you are hosting multiple domains in the same cluster.
+
+```yaml
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: multi-domain-policy
+spec:
+ rules:
+ - host: foo.bar.com
+ path: "*"
+ filters:
+ - name: foo-keycloak
+ - host: example.com
+ path: "*"
+ filters:
+ - name: example-auth0
+```
+
+## Filters Using Self-Signed Certificates
+
+The JWT and OAuth2 filters speak to other services over HTTP or HTTPS. If those services are configured to speak HTTPS using a self-signed certificate, attempting to talk to them will result in an error mentioning `ERR x509: certificate signed by unknown authority`. You can fix this by installing that self-signed certificate into the AES container by copying the certificate to `/usr/local/share/ca-certificates/` and then running `update-ca-certificates`. Note that the `aes` image sets `USER 1000` but `update-ca-certificates` needs to be run as root.
+
+The following Dockerfile will accomplish this procedure for you. When deploying Edge Stack, refer to that custom Docker image rather than to `docker.io/datawire/aes:$version$`
+
+```Dockerfile
+FROM docker.io/datawire/aes:$version$
+USER root
+COPY ./my-certificate.pem /usr/local/share/ca-certificates/my-certificate.crt
+RUN update-ca-certificates
+USER 1000
+```
+
+[JWT]: ./jwt
+[OAuth2]: ./oauth2
+[Plugin]: ./plugin
+[External]: ./external
+[API Keys]: ./apikeys
+[FilterPolicy resources]: ../../../custom-resources/getambassador/v3alpha1/filterpolicy
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
diff --git a/docs/edge-stack/latest/topics/using/filters/jwt.md b/docs/edge-stack/latest/topics/using/filters/jwt.md
new file mode 100644
index 000000000..bae8daa16
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/filters/jwt.md
@@ -0,0 +1,156 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using The JWT Filter
+
+The JWT filter type performs JWT validation on a [bearer token](https://tools.ietf.org/html/rfc6750) present in the HTTP header.
+If the bearer token JWT doesn't validate, or has insufficient scope, an RFC 6750-complaint error response with a `WWW-Authenticate`
+header is returned. The list of acceptable signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in
+`jwksURI`. Only RSA and `none` algorithms are supported.
+
+
+
+See the [JWT Filter API reference][] for an overview of all the supported fields.
+
+## JWT path-specific arguments
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: "example-jwt-filter"
+ arguments:
+ scope: # optional; default is []
+ - "scope-value-1"
+ - "scope-value-2"
+```
+
+`scope` is a list of OAuth scope values that Edge Stack will require to be listed in the [`scope` claim](https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-19#section-4.2). In addition to the normal values of the `scope` claim (a JSON string containing a space-separated list of values), the JWT Filter also accepts a JSON array of values.
+
+## Example configuration
+
+```yaml
+# Example results are for the JWT:
+#
+# eyJhbGciOiJub25lIiwidHlwIjoiSldUIiwiZXh0cmEiOiJzbyBtdWNoIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
+#
+# To save you some time decoding that JWT:
+#
+# header = {
+# "alg": "none",
+# "typ": "JWT",
+# "extra": "so much"
+# }
+# claims = {
+# "sub": "1234567890",
+# "name": "John Doe",
+# "iat": 1516239022
+# }
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: example-jwt-filter
+ namespace: example-namespace
+spec:
+ JWT:
+ jwksURI: "https://getambassador-demo.auth0.com/.well-known/jwks.json"
+ validAlgorithms:
+ - "none"
+ audience: "myapp"
+ requireAudience: false
+ injectRequestHeaders:
+ - name: "X-Fixed-String"
+ value: "Fixed String"
+ # result will be "Fixed String"
+ - name: "X-Token-String"
+ value: "{{ .token.Raw }}"
+ # result will be "eyJhbGciOiJub25lIiwidHlwIjoiSldUIiwiZXh0cmEiOiJzbyBtdWNoIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
+ - name: "X-Token-H-Alg"
+ value: "{{ .token.Header.alg }}"
+ # result will be "none"
+ - name: "X-Token-H-Typ"
+ value: "{{ .token.Header.typ }}"
+ # result will be "JWT"
+ - name: "X-Token-H-Extra"
+ value: "{{ .token.Header.extra }}"
+ # result will be "so much"
+ - name: "X-Token-C-Sub"
+ value: "{{ .token.Claims.sub }}"
+ # result will be "1234567890"
+ - name: "X-Token-C-Name"
+ value: "{{ .token.Claims.name }}"
+ # result will be "John Doe"
+ - name: "X-Token-C-Optional-Empty"
+ value: "{{ .token.Claims.optional }}"
+ # result will be ""; the header field will be set
+ # even if the "optional" claim is not set in the JWT.
+ - name: "X-Token-C-Optional-Unset"
+ value: "{{ if hasKey .token.Claims \"optional\" | not }}{{ doNotSet }}{{ end }}{{ .token.Claims.optional }}"
+ # Similar to "X-Token-C-Optional-Empty" above, but if the
+ # "optional" claim is not set in the JWT, then the header
+ # field won't be set either.
+ #
+ # Note that this does NOT remove/overwrite a client-supplied
+ # header of the same name. In order to distrust
+ # client-supplied headers, you MUST use a Lua script to
+ # remove the field before the Filter runs (see below).
+ - name: "X-Token-C-Iat"
+ value: "{{ .token.Claims.iat }}"
+ # result will be "1.516239022e+09" (don't expect JSON numbers
+ # to always be formatted the same as input; if you care about
+ # that, specify the formatting; see the next example)
+ - name: "X-Token-C-Iat-Decimal"
+ value: "{{ printf \"%.0f\" .token.Claims.iat }}"
+ # result will be "1516239022"
+ - name: "X-Token-S"
+ value: "{{ .token.Signature }}"
+ # result will be "" (since "alg: none" was used in this example JWT)
+ - name: "X-Authorization"
+ value: "Authenticated {{ .token.Header.typ }}; sub={{ .token.Claims.sub }}; name={{ printf \"%q\" .token.Claims.name }}"
+ # result will be: "Authenticated JWT; sub=1234567890; name="John Doe""
+ - name: "X-UA"
+ value: "{{ .httpRequestHeader.Get \"User-Agent\" }}"
+ # result will be: "curl/7.66.0" or
+ # "Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0"
+ # or whatever the requesting HTTP client is
+ errorResponse:
+ headers:
+ - name: "Content-Type"
+ value: "application/json"
+ - name: "X-Correlation-ID"
+ value: "{{ .httpRequestHeader.Get \"X-Correlation-ID\" }}"
+ # Regarding the "altErrorMessage" below:
+ # ValidationErrorExpired = 1<<4 = 16
+ # https://godoc.org/github.com/dgrijalva/jwt-go#StandardClaims
+ bodyTemplate: |-
+ {
+ "errorMessage": {{ .message | json " " }},
+ {{- if .error.ValidationError }}
+ "altErrorMessage": {{ if eq .error.ValidationError.Errors 16 }}"expired"{{ else }}"invalid"{{ end }},
+ "errorCode": {{ .error.ValidationError.Errors | json " "}},
+ {{- end }}
+ "httpStatus": "{{ .status_code }}",
+ "requestId": {{ .request_id | json " " }}
+ }
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ lua_scripts: |
+ function envoy_on_request(request_handle)
+ request_handle:headers():remove("x-token-c-optional-unset")
+ end
+```
+
+[JWT Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-jwt
diff --git a/docs/edge-stack/latest/topics/using/filters/oauth2.md b/docs/edge-stack/latest/topics/using/filters/oauth2.md
new file mode 100644
index 000000000..13aa7230c
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/filters/oauth2.md
@@ -0,0 +1,225 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Using The OAuth2 Filter
+
+The OAuth2 filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html). The filter is both:
+
+* An OAuth Client, which fetches resources from the Resource Server on the user's behalf.
+* Half of a Resource Server, validating the Access Token before allowing the request through to the upstream service, which implements the other half of the Resource Server.
+
+This is different from most OAuth implementations where the Authorization Server and the Resource Server are in the same security domain. With Ambassador Edge Stack, the Client and the Resource Server are in the same security domain, and there is an independent Authorization Server.
+
+
+
+See the [OAuth2 Filter API reference][] for an overview of all the supported fields.
+
+## The Ambassador authentication flow
+
+This is what the authentication process looks like at a high level when using Ambassador Edge Stack with an external identity provider. The use case is an end-user accessing a secured app service.
+
+
+
+### Some basic authentication terms
+
+For those unfamiliar with authentication, here is a basic set of definitions.
+
+* OpenID: is an [open standard](https://openid.net/) and [decentralized authentication protocol](https://en.wikipedia.org/wiki/OpenID). OpenID allows users to be authenticated by co-operating sites, referred to as "relying parties" (RP) using a third-party authentication service. End-users can create accounts by selecting an OpenID identity provider (such as Auth0, Okta, etc), and then use those accounts to sign onto any website that accepts OpenID authentication.
+* Open Authorization (OAuth): an open standard for [token-based authentication and authorization](https://oauth.net/) on the Internet. OAuth provides to clients a "secure delegated access" to server or application resources on behalf of an owner, which means that although you won't manage a user's authentication credentials, you can specify what they can access within your application once they have been successfully authenticated. The current latest version of this standard is OAuth 2.0.
+* Identity Provider (IdP): an entity that [creates, maintains, and manages identity information](https://en.wikipedia.org/wiki/Identity_provider) for user accounts (also referred to "principals") while providing authentication services to external applications (referred to as "relying parties") within a distributed network, such as the web.
+* OpenID Connect (OIDC): is an [authentication layer that is built on top of OAuth 2.0](https://openid.net/connect/), which allows applications to verify the identity of an end-user based on the authentication performed by an IdP, using a well-specified RESTful HTTP API with JSON as a data format. Typically an OIDC implementation will allow you to obtain basic profile information for a user that successfully authenticates, which in turn can be used for implementing additional security measures like Role-based Access Control (RBAC).
+* JSON Web Token (JWT): is a [JSON-based open standard for creating access tokens](https://jwt.io/), such as those generated from an OAuth authentication. JWTs are compact, web-safe (or URL-safe), and are often used in the context of implementing single sign-on (SSO) within federated applications and organizations. Additional profile information, claims, or role-based information can be added to a JWT, and the token can be passed from the edge of an application right through the application's service call stack.
+
+If you look back at the authentication process diagram, the function of the entities involved should now be much clearer.
+
+### Using an identity hub
+
+Using an identity hub or broker allows you to support many IdPs without having to code individual integrations with them. For example, [Auth0](https://auth0.com/docs/identityproviders) and [Keycloak](https://www.keycloak.org/docs/latest/server_admin/index.html#social-identity-providers) both offer support for using Google and GitHub as an IdP.
+
+An identity hub sits between your application and the IdP that authenticates your users, which not only adds a level of abstraction so that your application (and Ambassador Edge Stack) is isolated from any changes to each provider's implementation, but it also allows your users to chose which provider they use to authenticate (and you can set a default, or restrict these options).
+
+The Auth0 docs provide a guide for adding social IdP "[connections](https://auth0.com/docs/identityproviders)" to your Auth0 account, and the Keycloak docs provide a guide for adding social identity "[brokers](https://www.keycloak.org/docs/latest/server_admin/index.html#social-identity-providers)".
+
+## OAuth2 path-specific arguments
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec:
+ rules:
+ - host: "*"
+ path: "*"
+ filters:
+ - name: "example-oauth2-filter"
+ arguments:
+ scope: # optional; default is ["openid"] for `grantType=="AuthorizationCode"`; [] for `grantType=="ClientCredentials"` and `grantType=="Password"`
+ - "scopevalue1"
+ - "scopevalue2"
+ scopes: # deprecated; use 'scope' instead
+ insteadOfRedirect: # optional for "AuthorizationCode"; default is to do a redirect to the identity provider
+ ifRequestHeader: # optional; default is to return httpStatusCode for all requests that would redirect-to-identity-provider
+ name: "string" # required
+ negate: bool # optional; default is false
+ # It is invalid to specify both "value" and "valueRegex".
+ value: "string" # optional; default is any non-empty string
+ valueRegex: "regex" # optional; default is any non-empty string
+ # option 1:
+ httpStatusCode: integer # optional; default is 403 (unless `filters` is set)
+ # option 2 (deprecated - will be removed in future version):
+ filters: # optional; default is to use `httpStatusCode` instead
+ - name: "string" # required
+ namespace: "string" # optional; default is the same namespace as the FilterPolicy
+ ifRequestHeader: # optional; default to apply this filter to all requests matching the host & path
+ name: "string" # required
+ negate: bool # optional; default is false
+ # It is invalid to specify both "value" and "valueRegex".
+ value: "string" # optional; default is any non-empty string
+ valueRegex: "regex" # optional; default is any non-empty string
+ onDeny: "enum" # optional; default is "break"
+ onAllow: "enum" # optional; default is "continue"
+ arguments: DEPENDS # optional
+ sameSite: "enum" # optional; the SameSite attribute to set on cookies created by this filter. valid values include: "lax", "strict", "none". by default, no SameSite attribute is set, which typically allows the browser to decide the value.
+```
+
+ - `scope`: A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope.
+
+ If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed in the `FilterPolicy` argument.
+
+ If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+
+ As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+
+ The ordering of scope values does not matter, and is ignored.
+
+ - `scopes` is deprecated, and is equivalent to setting `scope`.
+
+ - `insteadOfRedirect`: An action to perform instead of redirecting
+ the User-Agent to the identity provider, when using `grantType: "AuthorizationCode"`.
+ By default, if the User-Agent does not have a currently-authenticated session,
+ then the Ambassador Edge Stack will redirect the User-Agent to the
+ identity provider. Setting `insteadOfRedirect` allows you to modify
+ this behavior. `insteadOfRedirect` does nothing when `grantType:
+ "ClientCredentials"`, because the Ambassador Edge Stack will never
+ redirect the User-Agent to the identity provider for the client
+ credentials grant type.
+ * If `insteadOfRedirect` is non-`null`, then by default it will
+ apply to all requests that would cause the redirect; setting the
+ `ifRequestHeader` sub-argument causes it to only apply to
+ requests that have the HTTP header field
+ `name` (case-insensitive) either set to (if `negate: false`) or
+ not set to (if `negate: true`)
+ + a non-empty string if neither `value` nor `valueRegex` are set
+ + the exact string `value` (case-sensitive) (if `value` is set)
+ + a string that matches the regular expression `valueRegex` (if
+ `valueRegex` is set). This uses [RE2][] syntax (always, not
+ obeying `regex_type` in the `Module`) but does
+ not support the `\C` escape sequence.
+ * By default, it serves an authorization-denied error page; by default HTTP 403 ("Forbidden"), but this can be configured by the `httpStatusCode` sub-argument.
+ * __DEPRECATED__ Instead of serving that simple error page, it can instead be configured to call out to a list of other Filters, by setting the `filters` list. The syntax and semantics of this list are the same as `.spec.rules[].filters` in a `FilterPolicy`. Be aware that if one of these filters modify the request rather than returning a response, then the request will be allowed through to the backend service, even though the `OAuth2` Filter denied it.
+ * It is invalid to specify both `httpStatusCode` and `filters`.
+
+## XSRF protection
+
+The `ambassador_xsrf.NAME.NAMESPACE` cookie is an opaque string that should be used as an XSRF token. Applications wishing to leverage the Ambassador Edge Stack in their XSRF attack protection should take two extra steps:
+
+ 1. When generating an HTML form, the server should read the cookie, and include a `` element in the form.
+ 2. When handling submitted form data should verify that the form value and the cookie value match. If they do not match, it should refuse to handle the request, and return an HTTP 4XX response.
+
+Applications using request submission formats other than HTML forms should perform analogous steps of ensuring that the value is present in the request duplicated in the cookie and also in either the request body or secure header field. A secure header field is one that is not `Cookie`, is not "[simple](https://www.w3.org/TR/cors/#simple-header)", and is not explicitly allowed by the CORS policy.
+
+
+
+ Prior versions of Ambassador Edge Stack did not have an ambassador_xsrf.NAME.NAMESPACE cookie, and instead required you to use the ambassador_session.NAME.NAMESPACE cookie. The ambassador_session.NAME.NAMESPACE cookie should no longer be used for XSRF-protection purposes.
+
+
+## RP-initiated logout
+
+When a logout occurs, it is often not enough to delete the Ambassador
+Edge Stack's session cookie or session data; after this happens, and the web
+browser is redirected to the Identity Provider to re-log-in, the
+Identity Provider may remember the previous login, and immediately
+re-authorize the user; it would be like the logout never even
+happened.
+
+To solve this, the Ambassador Edge Stack can use [OpenID Connect Session
+Management](https://openid.net/specs/openid-connect-session-1_0.html)
+to perform an "RP-Initiated Logout", where Edge Stack
+(the OpenID Connect "Relying Party" or "RP")
+communicates directly with Identity Providers that support OpenID
+Connect Session Management, to properly log out the user.
+Unfortunately, many Identity Providers do not support OpenID Connect
+Session Management.
+
+This is done by having your application direct the web browser `POST`
+*and navigate* to `/.ambassador/oauth2/logout`. There are 2
+form-encoded values that you need to include:
+
+ 1. `realm`: The `name.namespace` of the `Filter` that you want to log
+ out of. This may be submitted as part of the POST body, or may be set as a URL query parameter.
+ 2. `_xsrf`: The value of the `ambassador_xsrf.{{realm}}` cookie
+ (where `{{realm}}` is as described above). This must be set in the POST body, the URL query part will not be checked.
+
+### Example configurations
+
+```html
+
+```
+
+```html
+
+```
+
+Using JavaScript:
+
+```js
+function getCookie(name) {
+ var prefix = name + "=";
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = cookies[i].trimStart();
+ if (cookie.indexOf(prefix) == 0) {
+ return cookie.slice(prefix.length);
+ }
+ }
+ return "";
+}
+
+function logout(realm) {
+ var form = document.createElement('form');
+ form.method = 'post';
+ form.action = '/.ambassador/oauth2/logout?realm='+realm;
+ //form.target = '_blank'; // uncomment to open the identity provider's page in a new tab
+
+ var xsrfInput = document.createElement('input');
+ xsrfInput.type = 'hidden';
+ xsrfInput.name = '_xsrf';
+ xsrfInput.value = getCookie("ambassador_xsrf."+realm);
+ form.appendChild(xsrfInput);
+
+ document.body.appendChild(form);
+ form.submit();
+}
+```
+
+## Redis
+
+The Ambassador Edge Stack relies on Redis to store short-lived authentication credentials and rate limiting information. If the Redis data store is lost, users will need to log back in and all existing rate-limits would be reset.
+
+## Further reading
+
+In this architecture, Ambassador Edge Stack is functioning as an Identity Aware Proxy in a Zero Trust Network. For more about this security architecture, read the [BeyondCorp security architecture whitepaper](https://ai.google/research/pubs/pub43231) by Google.
+
+The ["How-to" section](../../../../howtos/) has detailed tutorials on integrating Ambassador with a number of Identity Providers.
+
+[OAuth2 Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-oauth2
+[RE2]: https://github.com/google/re2/wiki/Syntax
diff --git a/docs/edge-stack/latest/topics/using/filters/plugin.md b/docs/edge-stack/latest/topics/using/filters/plugin.md
new file mode 100644
index 000000000..9c5781210
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/filters/plugin.md
@@ -0,0 +1,33 @@
+# Developing Plugin Filters
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled to a `.so` file, which you load into the Edge Stack container at `/etc/ambassador-plugins/${NAME}.so`. The [Filter Development Guide](../../../../howtos/filter-dev-guide) contains a tutorial on developing filters.
+
+
+
+See the [Plugin Filter API reference][] for an overview of all the supported fields.
+
+## The Plugin interface
+
+This code is written in the Go programming language and must be compiled with the exact same compiler settings as Edge Stack (any overlapping libraries used must have their versions match exactly). This information is documented in the `/ambassador/aes-abi.txt` file in the AES docker image.
+
+Plugins are compiled with `go build -buildmode=plugin -trimpath` and must have a `main.PluginMain` function with the signature `PluginMain(w http.ResponseWriter, r *http.Request)`:
+
+```go
+package main
+
+import (
+ "net/http"
+)
+
+func PluginMain(w http.ResponseWriter, r *http.Request) { … }
+```
+
+`*http.Request` is the incoming HTTP request that can be mutated or intercepted, which is done by `http.ResponseWriter`.
+
+Headers can be mutated by calling `w.Header().Set(HEADERNAME, VALUE)`.
+Finalize changes by calling `w.WriteHeader(http.StatusOK)`.
+
+If you call `w.WriteHeader()` with any value other than 200 (`http.StatusOK`) instead of modifying the request, the plugin has
+taken over the request, and the request will not be sent to your backend service. You can call `w.Write()` to write the body of an error page.
+
+[Plugin Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-plugin
diff --git a/docs/edge-stack/latest/topics/using/licenses.md b/docs/edge-stack/latest/topics/using/licenses.md
new file mode 100644
index 000000000..648f4deaf
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/licenses.md
@@ -0,0 +1,54 @@
+# $productName$ Licenses
+
+$productName$ requires a valid Enterprise license or Community license to start up. The Community license allows you to use $productName$ for free with certain restrictions and the Enterprise license lifts these restrictions for further use of premium features.
+
+For more details on the different licenses, please visit the [editions page](/editions).
+
+## Enterprise License
+To obtain an Enterprise license, you can [reach out to our sales team][] for more information.
+
+If you have any questions regarding your Enterprise license, or require an air gapped license, please to reach out to [support][].
+
+## Applying a License
+The process for applying a license is the same, regardless of which plan you choose:
+
+* Enterprise License: If you have already purchased an Enterprise plan, you can follow the steps below to connect your clusters to Ambassador Cloud. Your Enterprise license will automatically apply to all clusters that you connect. If you believe you have an Enterprise license, but this is not reflected in Ambassador Cloud after connecting your clusters, please reach out to [support][].
+
+* Community License: If you wish to utilize a free Community license for your Edge Stack clusters, you can follow the steps below to connect your clusters to Ambassador Cloud, and the Community license will be automatically applied.
+
+
+1. Installing the cloud connect token
+
+ You can follow the instructions on [the quickstart guide][] to get signed into [Ambassador Cloud][] and obtain a cloud connect token for your installation of $productName$ if you don't already have one.
+ This will let $productName$ request and renew your license from Ambassador Cloud.
+
+ The Cloud Connect Token is a `ConfigMap` that you will install in your Kubernetes cluster and looks like this:
+
+ ```yaml
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: edge-stack-agent-cloud-token
+ namespace: ambassador
+ data:
+ CLOUD_CONNECT_TOKEN:
+ ```
+
+2. Install the Cloud Connect Token
+
+ If you are using Helm, you can use Helm to manage your installation.
+
+ ```bash
+ helm install edge-stack --namespace ambassador datawire/edge-stack --set emissary-ingress.createDefaultListeners=true --set emissary-ingress.agent.cloudConnectToken=
+ ```
+
+ If you do not want to use Helm, then you can apply the Cloud Connect Token with raw yaml instead.
+
+ ```bash
+ kubectl create configmap --namespace ambassador edge-stack-agent-cloud-token --from-literal=CLOUD_CONNECT_TOKEN=
+ ```
+
+[reach out to our sales team]: /contact-us/
+[the quickstart guide]: ../../../tutorials/getting-started
+[Ambassador Cloud]: https://app.getambassador.io/cloud/
+[support]: https://support.datawire.io
diff --git a/docs/edge-stack/latest/topics/using/rate-limits/index.md b/docs/edge-stack/latest/topics/using/rate-limits/index.md
new file mode 100644
index 000000000..b6e90faa7
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/rate-limits/index.md
@@ -0,0 +1,193 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Basic rate limiting
+
+Rate limiting in $productName$ is composed of two parts:
+
+* The [`RateLimitService`] resource tells $productName$ what external service
+ to use for rate limiting.
+
+ If $productName$ cannot contact the rate limit service, it will allow the request to be processed as if there were no rate limit service configuration.
+
+* _Labels_ that get attached to requests. A label is basic metadata that
+ is used by the `RateLimitService` to decide which limits to apply to
+ the request.
+
+
+ These labels require Mapping resources with apiVersion
+ getambassador.io/v2 or newer — if you're updating an old installation, check the
+ apiVersion!
+
+
+Labels are grouped according to _domain_ and _group_:
+
+```yaml
+labels:
+ "domain1":
+ - "group1":
+ - "my_label_specifier_1"
+ - "my_label_specifier_2"
+ - "group2":
+ - "my_label_specifier_3"
+ - "my_label_specifier_4"
+ "domain2":
+ - ...
+```
+
+## Attaching labels to requests
+
+There are two ways of setting labels on a request:
+
+1. You can set labels on an individual [`Mapping`](../mappings). These labels
+ will only apply to requests that use that `Mapping`.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Mapping
+ metadata:
+ name: foo-mapping
+ spec:
+ hostname: "*"
+ prefix: /foo/
+ service: foo
+ labels:
+ "domain1":
+ - "group1":
+ - "my_label_specifier_1"
+ - "my_label_specifier_2"
+ - "group2":
+ - "my_label_specifier_3"
+ - "my_label_specifier_4"
+ "domain2":
+ - ...
+ ```
+
+2. You can set global labels in the [`ambassador` `Module`](../../running/ambassador).
+ These labels will apply to _every_ request that goes through $productName$.
+
+ ```yaml
+ ---
+ apiVersion: getambassador.io/v3alpha1
+ kind: Module
+ metadata:
+ name: ambassador
+ spec:
+ config:
+ default_labels:
+ "domain1":
+ defaults:
+ - "my_label_specifier_a"
+ - "my_label_specifier_b"
+ "domain2":
+ defaults:
+ - "my_label_specifier_c"
+ - "my_label_specifier_d"
+ ```
+
+ If a `Mapping` and the defaults both give label groups for the same domain, the
+ default labels are prepended to each label group from the `Mapping`. If the `Mapping`
+ does not give any labels for that domain, the global labels are placed into a new
+ label group named "default" for that domain.
+
+Each label group is a list of labels; each label is a key/value pair. Since the label
+group is a list rather than a map:
+- it is possible to have multiple labels with the same key, and
+- the order of labels matters.
+
+> Note: The terminology used by the Envoy documentation differs from
+> the terminology used by $productName$:
+>
+> | $productName$ | Envoy |
+> |-----------------|-------------------|
+> | label group | descriptor |
+> | label | descriptor entry |
+> | label specifier | rate limit action |
+
+The `Mapping`s' listing of the groups of specifiers have names for the
+groups; the group names are useful for humans dealing with the YAML,
+but are ignored by $productName$, all $productName$ cares about are the
+*contents* of the groupings of label specifiers.
+
+There are 5 types of label specifiers in $productName$:
+
+
+
+1. `source_cluster`
+
+ ```yaml
+ source_cluster:
+ key: source_cluster
+ ```
+
+ Sets the label `source_cluster=«Envoy source cluster name»"`. The Envoy
+ source cluster name is the name of the Envoy cluster that the request came
+ in on.
+
+ The syntax of this label currently _requires_ `source_cluster: {}`.
+
+2. `destination_cluster`
+
+ ```yaml
+ destination_cluster:
+ key: destination_cluster
+ ```
+
+ Sets the label `destination_cluster=«Envoy destination cluster name»"`. The Envoy
+ destination cluster name is the name of the Envoy cluster to which the `Mapping`
+ routes the request. You can get the name for a cluster from the
+ [diagnostics service](../../running/diagnostics/).
+
+ The syntax of this label currently _requires_ `destination_cluster: {}`.
+
+3. `remote_address`
+
+ ```yaml
+ remote_address:
+ key: remote_address
+ ```
+
+ Sets the label `remote_address=«IP address of the client»"`. The IP address of
+ the client will be taken from the `X-Forwarded-For` header, to correctly manage
+ situations with L7 proxies. This requires that $productName$ be correctly
+ [configured to communicate](../../../howtos/configure-communications).
+
+ The syntax of this label currently _requires_ `remote_address: {}`.
+
+4. `request_headers`
+
+ ```yaml
+ request_headers:
+ header_name: "header-name"
+ key: mykey
+ ```
+
+ If a header named `header-name` is present, set the label `mykey=«value of the header»`.
+ If no header named `header-name` is present, **the entire label group is dropped**.
+
+5. `generic_key`
+
+ ```yaml
+ generic_key:
+ key: mykey
+ value: myvalue
+ ```
+
+ Sets the label `«mykey»=«myval»`. Note that supplying a `key` is supported only
+ with the Envoy V3 API.
+
+## Rate limiting requests based on their labels
+
+This is determined by your `RateLimitService` implementation.
+
+$AESproductName$ provides a `RateLimitService` implementation that is
+configured by a `RateLimit` custom resource.
+
+See the [$AESproductName$ RateLimit Reference](./rate-limits) for information on how
+to configure `RateLimit`s in $AESproductName$.
+
+See the [Basic Rate Limiting tutorial](../../../howtos/rate-limiting-tutorial) for an
+example `RateLimitService` implementation for $OSSproductName$.
diff --git a/docs/edge-stack/latest/topics/using/rate-limits/rate-limits.md b/docs/edge-stack/latest/topics/using/rate-limits/rate-limits.md
new file mode 100644
index 000000000..f764000bc
--- /dev/null
+++ b/docs/edge-stack/latest/topics/using/rate-limits/rate-limits.md
@@ -0,0 +1,534 @@
+# Rate limiting reference
+
+Rate limiting in $productName$ is composed of two parts:
+
+* Labels that get attached to requests; a label is basic metadata that
+ is used by the `RateLimitService` to decide which limits to apply to
+ the request.
+* `RateLimit`s configure $productName$'s built-in
+ `RateLimitService`, and set limits based on the labels on the
+ request.
+
+
+> This page covers using `RateLimit` resources to configure $productName$
+ to rate limit requests. See the [Basic Rate Limiting article](../) for
+ information on adding labels to requests.
+
+
+## Rate limiting requests based on their labels
+
+A `RateLimit` resource defines a list of limits that apply to
+different requests.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: example-limits
+spec:
+ domain: "my_domain"
+ limits:
+ - name: per-minute-limit # optional; default is the `$name.$namespace-$idx` where name is the name of the CRD and idx is the index into the limits array
+ action: Enforce # optional; default to "Enforce". valid values are "Enforce" and "LogOnly", case insensitive.
+ pattern:
+ - "my_key1": "my_value1"
+ "my_key2": "my_value2"
+ - "my_key3": "my_value3"
+ rate: 5
+ unit: "minute"
+ injectRequestHeaders: # optional
+ - name: "header-name-string-1" # required
+ value: "go-template-string" # required
+ - name: "header-name-string-2" # required
+ value: "go-template-string" # required
+ injectResponseHeaders: # optional
+ - name: "header-name-string-1" # required
+ value: "go-template-string" # required
+ errorResponse: # optional
+ headers: # optional; default is [], adding no additional headers
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ bodyTemplate: "string" # optional; default is "", returning no response body
+ - name: per-second-limit
+ action: Enforce
+ pattern:
+ - "my_key4": "" # check the key but not the value
+ - "my_key5": "*" # check the key but not the value
+ rate: 5
+ unit: "second"
+ ...
+```
+
+It makes no difference whether limits are defined together in one
+`RateLimit` resource or are defined separately in many `RateLimit`
+resources.
+
+
+
+ - `name`: The symbolic name for this ratelimit. Used to set dynamic metadata that can be referenced in the Envoy access log.
+
+ - `action`: Each limit has an *action* that it will take when it is exceeded. Actions include:
+
+ * `Enforce` - enforce this limit on the client by returning HTTP 429. This is the default action.
+ * `LogOnly` - do not enforce this limit on the client, and allow the client request upstream if no other limit applies.
+
+ - `pattern`: Each limit has a *pattern* that matches against a label
+ group on a request to decide if that limit should apply to that
+ request. For a pattern to match, the request's label group must
+ start with exactly the labels specified in the pattern, in order.
+ If a label in a pattern has an empty string or `"*"` as the value,
+ then it only checks the key of that label on the request; not the
+ value. If a list item in the pattern has multiple key/value pairs,
+ if any of them match the label then it is considered a match.
+
+ For example, the pattern
+
+ ```yaml
+ pattern:
+ - "key1": "foo"
+ "key1": "bar"
+ - "key2": ""
+ ```
+
+ matches the label group
+
+ ```yaml
+ - key1: foo
+ - key2: baz
+ - otherkey: knob
+ ```
+
+ and
+
+ ```yaml
+ - key1: bar
+ - key2: baz
+ - otherkey: knob
+ ```
+
+ but not the label group
+
+ ```yaml
+ - key0: frob
+ - key1: foo
+ - key2: baz
+ ```
+
+ If a label group is matched by multiple patterns, the pattern with
+ the longest list of items wins.
+
+ If a request has multiple label groups, then multiple limits may apply
+ to that request; if *any* of the limits are being hit, then Ambassador
+ will reject the request as an HTTP 429.
+
+ - `rate`, `unit`: The limit itself is specified as an integer number
+ of requests per a unit of time. Valid units of time are `second`,
+ `minute`, `hour`, or `day` (all case-insensitive).
+
+ So for example
+
+ ```yaml
+ rate: 5
+ unit: minute
+ ```
+
+ would allow 5 requests per minute, and any requests in excess of
+ that would result in HTTP 429 errors. Note that the limit is
+ tracked in terms of wall clock minutes and not a sliding
+ window. For example if 5 requests happen 59 seconds into the
+ current wall clock minute, then clients only need to wait a second
+ in order to make another 5 requests.
+
+ - `burstFactor`: The optional `burstFactor` field changes enforcement
+ of ratelimits in two ways:
+
+ * A `burstFactor` of `N` will allow unused requests from a window
+ of `N` time units to be rolled over and included in the current
+ request limit. This will effectively result in two separate
+ ratelimits being applied depending on the dynamic behavior of
+ clients. Clients that only make occasional bursts will end up
+ with an effective ratelimit of `burstFactor` * `rate`, whereas
+ clients that make requests continually will be limited to just
+ `rate`. For example:
+
+ ```yaml
+ rate: 5
+ unit: minute
+ burstFactor: 5
+ ```
+
+ would allow bursts of up to 25 request per minute, but only
+ permit continual usage of 5 requests per minute.
+
+ * A `burstFactor` of `1` is logically very similar to no
+ `burstFactor` with one key difference. When `burstFactor` is
+ specified, requests are tracked with a sliding window rather than
+ in terms of wall clock minutes. For example:
+
+ ```yaml
+ rate: 5
+ unit: minute
+ burstFactor: 1
+ ```
+
+ With*out* the `burstFactor` of 1, the above limit would permit up
+ to 5 requests within any wall clock minute. *With* the
+ `burstFactor` of 1 it means that no more than 5 requests are
+ permitted within any 1 minute sliding window.
+
+ Note that the `burstFactor` field only works when the
+ `AES_RATELIMIT_PREVIEW` environment variable is set to `true`.
+
+ - `injectRequestHeaders`, `injectResponseHeaders`: If this limit's
+ pattern matches the request, then `injectRequestHeaders` injects
+ HTTP header fields in to the request before sending it to the
+ upstream service (assuming the limit even allows the request to go
+ to the upstream service), and `injectResponseHeaders` injects
+ headers in to the response sent back to the client (whether the
+ response came from the upstream service or is an HTTP 429 response
+ because it got rate limited). This is very similar to
+ `injectRequestHeaders` in a [`JWT` Filter][]. The header value is
+ specified as a [Go `text/template`][] string, with the following
+ data made available to it:
+
+ * `.RateLimitResponse.OverallCode` → `int` : `1` for OK, `2` for
+ OVER_LIMIT.
+ * `.RateLimitResponse.Statuses` →
+ [`[]*RateLimitResponse_DescriptorStatus]`]`v2.RateLimitResponse_DescriptorStatus`
+ The itemized status codes for each limit that was selected for
+ this request.
+ * `.RetryAfter` → `time.Duration` the amount of time until all of
+ the limits would allow access again (0 if they all currently
+ allow access).
+
+ Also available to the template are the [standard functions available
+ to Go `text/template`s][Go `text/template` functions], as well as:
+
+ * a `hasKey` function that takes the a string-indexed map as arg1,
+ and returns whether it contains the key arg2. (This is the same
+ as the [Sprig function of the same name][Sprig `hasKey`].)
+
+ * a `doNotSet` function that causes the result of the template to
+ be discarded, and the header field to not be adjusted. This is
+ useful for only conditionally setting a header field; rather
+ than setting it to an empty string or `""`. Note that
+ this does _not_ unset an existing header field of the same name.
+
+ - `errorResponse` allows templating the error response, overriding the default json error format. Make sure you validate and test your template, not to generate server-side errors on top of client errors.
+ * `headers` sets extra HTTP header fields in the error response. The value is specified as a [Go `text/template`][] string, with the same data made available to it as `bodyTemplate` (below). It does not have access to the `json` function.
+ * `bodyTemplate` specifies body of the error; specified as a [Go `text/template`][] string, with the following data made available to it:
+
+ * `.status_code` → `integer` the HTTP status code to be returned
+ * `.message` → `string` the error message string
+ * `.request_id` → `string` the Envoy request ID, for correlation (hidden from `{{ . | json "" }}` unless `.status_code` is in the 5XX range)
+ * `.RateLimitResponse.OverallCode` → `int` : `1` for OK, `2` for
+ OVER_LIMIT.
+ * `.RateLimitResponse.Statuses` →
+ [`[]*RateLimitResponse_DescriptorStatus]`]`v3.RateLimitResponse_DescriptorStatus`
+ The itemized status codes for each limit that was selected for
+ this request.
+ * `.RetryAfter` → `time.Duration` the amount of time until all of
+ the limits would allow access again (0 if they all currently
+ allow access).
+
+ Also availabe to the template are the [standard functions
+ available to Go `text/template`s][Go `text/template` functions],
+ as well as:
+
+ * a `json` function that formats arg2 as JSON, using the arg1
+ string as the starting indentation. For example, the
+ template `{{ json "indent>" "value" }}` would yield the
+ string `indent>"value"`.
+
+[`JWT` Filter]: ../../filters/jwt
+[Go `text/template`]: https://golang.org/pkg/text/template/
+[Go `text/template` functions]: https://golang.org/pkg/text/template/#hdr-Functions
+[Sprig `hasKey`]: https://masterminds.github.io/sprig/dicts.html#haskey
+
+## Logging RateLimits
+
+It is often desirable to know which RateLimit, if any, is applied to a client's request. This can be achieved by leveraging dynamic metadata available to Envoy's access log.
+
+The following dynamic metadata keys are available under the `envoy.filters.http.ratelimit` namespace. See https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage for more on Envoy's access log format.
+
+* `aes.ratelimit.name` - The symbolic `name` of the `Limit` on a `RateLimit` object that triggered the ratelimit action.
+* `aes.ratelimit.action` - The action that the `Limit` took. Possible values include `Enforce` and `LogOnly`. When the action is `Enforce`, the client was ratelimited with HTTP 429. When the action is `LogOnly`, the ratelimit was not enforced and the client's request was allowed upstream.
+* `aes.ratelimit.retry_after` - The time in seconds until the `Limit` resets. Equivalent to the value of the `Retry-After` returned to the client if the limit was enforced.
+
+If a `Limit` with a `LogOnly` action is exceeded and there are no other non-`LogOnly` `Limit`s that were exceeded, the request will be allowed upstream and that `Limit` will available as dynamic metadata above.
+
+Note that if multiple `Limit`s were exceeded by a request, only the `Limit` with the longest time until reset (i.e. its Retry-After value) will be available as dynamic metadata above. The only exception is if the `Limit` with the longest time until reset is `LogOnly` and there exists another non-`LogOnly` limit that was exceeded. In that case, the non-`LogOnly` `Limit` will be available as dynamic metadata. This ensures that `LogOnly` `Limits` will never prevent non-`LogOnly` `Limits` from enforcing or from being observable in the Envoy access log.
+
+### An example access log specification for RateLimit dynamic metadata
+
+Module:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ envoy_log_format: 'ratelimit %DYNAMIC_METADATA(envoy.filters.http.ratelimit:aes.ratelimit.name)% took action %DYNAMIC_METADATA(envoy.filters.http.ratelimit:aes.ratelimit.action)%'
+```
+
+## RateLimit examples
+
+### An example service-level rate limit
+
+The following `Mapping` resource will add a
+`my_default_generic_key_label` `generic_key` label to every request to
+the `foo-app` service:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: foo-app
+spec:
+ hostname: "*"
+ prefix: /foo/
+ service: foo
+ labels:
+ ambassador:
+ - label_group:
+ - generic_key:
+ value: my_default_generic_key_label
+```
+
+You can then create a default RateLimit for every request that matches
+this label:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: default-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern:
+ - generic_key: "my_default_generic_key_label"
+ rate: 10
+ unit: minute
+```
+
+> Tip: For testing purposes, it is helpful to configure per-minute
+> rate limits before switching the rate limits to per second or per
+> hour.
+
+### An example with multiple labels
+
+Mappings can have multiple `labels` which annotate a given request.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: catalog
+spec:
+ hostname: "*"
+ prefix: /catalog/
+ service: catalog
+ labels:
+ ambassador: # the label domain
+ - string_request_label: # the label group name -- useful for humans, ignored by Ambassador
+ - generic_key: # this is a generic_key label
+ value: catalog # annotate the request with `generic_key=catalog`
+ - header_request_label: # another label group name
+ - request_headers: # this is a label using request headers
+ key: headerkey # annotate the request with `headerkey=the specific HTTP method used`
+ header_name: ":method" # if the :method header is somehow unset, the whole group will be dropped.
+ - multi_request_label_group:
+ - request_headers:
+ key: authorityheader
+ header_name: ":authority"
+ - request_headers:
+ key: xuserheader
+ header_name: "x-user" # again, if x-user is not present, the _whole group_ is dropped
+```
+
+Let's digest the above example:
+
+* Request labels must be part of the "ambassador" label domain. Or
+ rather, it must match the domain in your
+ `RateLimitService.spec.domain` which defaults to
+ `Module.spec.default_label_domain` which defaults to `ambassador`;
+ but normally you should accept the default and just accept that the
+ domain on the Mappings must be set to "ambassador".
+* Each label must have a name, e.g., `one_request_label`
+* The `string_request_label` simply adds the string `catalog` to every
+ incoming request to the given mapping. The string is referenced
+ with the key `generic_key`.
+* The `header_request_label` adds a specific HTTP header value to the
+ request, in this case, the method. Note that HTTP/2 request headers
+ must be used here (e.g., the `host` header needs to be specified as
+ the `:authority` header).
+* Multiple labels can be part of a single named label, e.g.,
+ `multi_request_label` specifies two different headers to be added
+* When an HTTP header is not present, the entire named label is
+ omitted. The `omit_if_not_present: true` is an explicit notation to
+ remind end-users of this limitation. `false` is *not* a supported
+ value.
+
+### An example with multiple limits
+
+Labels can be grouped. This allows for a single request to count
+against multiple different `RateLimit` resources. For example,
+imagine the following scenario:
+
+1. Users should be limited on the total number of requests that can be
+ sent to a set of endpoints
+2. On a specific service, stricter limits are desirable
+
+The following `Mapping` resources could be configured:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: foo-app
+spec:
+ hostname: "*"
+ prefix: /foo/
+ service: foo
+ labels:
+ ambassador:
+ - foo-app_label_group:
+ - generic_key:
+ value: foo-app
+ - total_requests_group:
+ - remote_address
+ remote_address: {} # this is _required_ at present
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Mapping
+metadata:
+ name: bar-app
+spec:
+ hostname: "*"
+ prefix: /bar/
+ service: bar
+ labels:
+ ambassador:
+ - bar-app_label_group:
+ - generic_key:
+ value: bar-app
+ - total_requests_group:
+ - remote_address
+ remote_address: {} # this is _required_ at present
+```
+
+Now requests to the `foo-app` and the `bar-app` would be labeled with
+```yaml
+- "generic_key": "foo-app"
+- "remote_address": "10.10.11.12"
+```
+and
+```yaml
+- "generic_key": "bar-app"
+- "remote_address": "10.10.11.12"
+```
+respectively. `RateLimit`s on these two services could be created as
+such:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: foo-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern: [{generic_key: "foo-app"}]
+ rate: 10
+ unit: second
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: bar-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern: [{generic_key: "bar-app"}]
+ rate: 20
+ unit: second
+---
+apiVersion: getambassador.io/v3alpha1
+kind: RateLimit
+metadata:
+ name: user-rate-limit
+spec:
+ domain: ambassador
+ limits:
+ - pattern: [{remote_address: "*"}]
+ rate: 100
+ unit: minute
+```
+
+### An example with global labels and groups
+
+Global labels are prepended to every single label group. In the above
+example, if the following global label was added in the `ambassador`
+Module:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Module
+metadata:
+ name: ambassador
+spec:
+ config:
+ default_label_domain: ambassador
+ default_labels:
+ ambassador:
+ defaults:
+ - generic_key:
+ value: "my_default_label"
+```
+
+The labels metadata would change
+
+ - from
+ ```yaml
+ - "generic_key": "foo-app"
+ - "remote_address": "10.10.11.12"
+ ```
+ to
+ ```yaml
+ - "generic_key": "my_default_label"
+ - "generic_key": "foo-app"
+ - "remote_address": "10.10.11.12"
+ ```
+
+and
+
+ - from
+ ```yaml
+ - "generic_key": "bar-app"
+ - "remote_address": "10.10.11.12"
+ ```
+ to
+ ```yaml
+ - "generic_key": "my_default_label"
+ - "generic_key": "bar-app"
+ - "remote_address": "10.10.11.12"
+ ```
+
+respectively.
+
+And thus our `RateLimit`s would need to change to appropriately handle
+the new labels.
diff --git a/docs/edge-stack/latest/tutorials/getting-started.md b/docs/edge-stack/latest/tutorials/getting-started.md
new file mode 100644
index 000000000..f7f8a3481
--- /dev/null
+++ b/docs/edge-stack/latest/tutorials/getting-started.md
@@ -0,0 +1,158 @@
+---
+description: "A simple three step guide to installing $productName$ and quickly get started routing traffic from the edge of your Kubernetes cluster to your services."
+---
+
+import Alert from '@material-ui/lab/Alert';
+import GettingStartedEdgeStack21Tabs from './gs-tabs'
+
+# $productName$ quick start
+
+
+
Contents
+
+- [1. Installation](#1-installation)
+- [Getting a license from Ambassador Cloud](#getting-a-license-from-ambassador-cloud)
+- [2. Routing traffic from the edge](#2-routing-traffic-from-the-edge)
+- [What's next?](#img-classos-logo-srcimageslogopng-whats-next)
+
+
+
+## 1. Installation
+
+### Getting a license from Ambassador Cloud
+
+We'll start by installing $productName$ into your cluster.
+
+$productName$ requires a [license](../../topics/using/licenses) to function, so the first step is getting one to use while installing. If you are in air-gapped environment, please [contact sales](https://www.getambassador.io/contact-us).
+
+1. Log in to [Ambassador Cloud](https://app.getambassador.io/cloud/edge-stack/license/existing/) with GitHub, GitLab or Google and select your team account.
+
+2. Follow the prompts to name the cluster and click **Generate Key**.
+
+3. Either follow the installation instructions there, or copy the token out and follow along here.
+
+4. Once your cluster is connected to Ambassador Cloud, a community license is automatically applied.
+
+**We recommend using Helm** to install but there are other options below to choose from. Please replace `` below with your token from Ambassador Cloud.
+
+
+
+Success! At this point, you have installed $productName$. Now let's get some traffic flowing to your services.
+
+## 2. Routing traffic from the edge
+
+$productName$ uses Kubernetes Custom Resource Definitions (CRDs) to declaratively define its desired state. The workflow you are going to build uses a simple demo app, a **`Listener` CRD**, and a **`Mapping` CRD**. The `Listener` CRD tells $productName$ what port to listen on, and the `Mapping` CRD tells $productName$ how to route incoming requests by host and URL path from the edge of your cluster to Kubernetes services.
+
+1. Start by creating a `Listener` resource for HTTP on port 8080:
+
+ ```
+ kubectl apply -f - <The Service and Deployment are created in your default namespace. You can use kubectl get services,deployments quote to see their status.
+
+3. Generate the YAML for a `Mapping` to tell $productName$ to route all traffic inbound to the `/backend/` path to the `quote` Service.
+
+ In this step, we'll be using the Mapping Editor, which you can find in the service details view of your [Ambassador Cloud connected installation](#getting-a-license-from-ambassador-cloud).
+ Open your browser to https://app.getambassador.io/cloud/services/quote/details and click on **New Mapping**.
+
+ Default options are automatically populated. **Enable and configure the following settings**, then click **Generate Mapping**:
+ - **Path Matching**: `/backend/`
+ - **OpenAPI Docs**: `/.ambassador-internal/openapi-docs`
+
+ 
+
+ Whether you decide to automatically push the change to Git for this newly create Mapping resource or not, the resulting Mapping should be similar to the example below.
+
+ **Apply this YAML to your target cluster now.**
+
+ ```yaml
+ kubectl apply -f - <Victory! You have created your first $productName$ Mapping, routing a request from your cluster's edge to a service!
+
+## What's next?
+
+Explore some of the popular tutorials on $productName$:
+
+* [Intro to Mappings](../../topics/using/intro-mappings/): declaratively routes traffic from
+the edge of your cluster to a Kubernetes service
+* [Host resource](../../topics/running/host-crd/): configure a hostname and TLS options for your ingress.
+* [Rate Limiting](../../topics/using/rate-limits/rate-limits/): create policies to control sustained traffic loads
+
+$productName$ has a comprehensive range of [features](/features/) to
+support the requirements of any edge microservice.
+
+To learn more about how $productName$ works, read the [$productName$ Story](../../about/why-ambassador).
diff --git a/docs/edge-stack/latest/tutorials/gs-tabs.js b/docs/edge-stack/latest/tutorials/gs-tabs.js
new file mode 100644
index 000000000..6cbeab1bc
--- /dev/null
+++ b/docs/edge-stack/latest/tutorials/gs-tabs.js
@@ -0,0 +1,132 @@
+import AppBar from '@material-ui/core/AppBar';
+import Box from '@material-ui/core/Box';
+import Tab from '@material-ui/core/Tab';
+import Tabs from '@material-ui/core/Tabs';
+import { makeStyles } from '@material-ui/core/styles';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import CodeBlock from '../../../../../src/components/CodeBlock';
+import Icon from '../../../../../src/components/Icon';
+
+function TabPanel(props) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {/*Helm 3 token install instructions*/}
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Helm. The slideout contains instructions with a
+ unique cloud-connect-token that is used to connect your
+ cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'helm upgrade ambassador --namespace ambassador datawire/ambassador \\' +
+ '\n' +
+ ' --set agent.cloudConnectToken=$TOKEN && \\' +
+ '\n' +
+ 'kubectl -n ambassador wait --for condition=available --timeout=90s deploy -lproduct=aes'}
+
+
+
+
+ {/*Helm 2 token install instructions*/}
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Helm. The slideout contains instructions with a
+ unique cloud-connect-token that is used to connect your
+ cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'helm upgrade --namespace ambassador ambassador datawire/ambassador \\' +
+ '\n' +
+ ' --set crds.create=false --set agent.cloudConnectToken=$TOKEN && \\' +
+ '\n' +
+ 'kubectl -n ambassador wait --for condition=available --timeout=90s deploy -lproduct=aes'}
+
+
+
+
+ {/*YAML token install instructions*/}
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Kubernetes YAML. The slideout contains instructions
+ with a unique cloud-connect-token that is used to connect
+ your cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'kubectl create configmap -n ambassador ambassador-agent-cloud-token \\' +
+ '\n' +
+ ' --from-literal=CLOUD_CONNECT_TOKEN=$TOKEN'}
+
+
+
+
+ {/*edgectl token install instructions*/}
+ Connecting $productName$ that was installed via edgectl is
+ identical to the Kubernetes YAML procedure.
+
+ Log in to{' '}
+ Ambassador Cloud.
+ Click Connect my cluster to Ambassador Cloud, then{' '}
+ Connect via Kubernetes YAML. The slideout contains instructions
+ with a unique cloud-connect-token that is used to connect
+ your cluster to your Ambassador Cloud account.
+
+ Run the following command, replacing $TOKEN
+ with your token:
+
+ {'kubectl create configmap -n ambassador ambassador-agent-cloud-token \\' +
+ '\n' +
+ ' --from-literal=CLOUD_CONNECT_TOKEN=$TOKEN'}
+
+
+
+ );
+}
diff --git a/docs/edge-stack/latest/versions.yml b/docs/edge-stack/latest/versions.yml
new file mode 100644
index 000000000..5c35d393d
--- /dev/null
+++ b/docs/edge-stack/latest/versions.yml
@@ -0,0 +1,35 @@
+# branch info
+branch: release/v3.9
+
+# self
+version: 3.9.0
+productName: "Ambassador Edge Stack"
+productNamePlural: "Ambassador Edge Stacks"
+productNamespace: ambassador
+productDeploymentName: edge-stack
+productHelmName: edge-stack
+
+# OSS (not self)
+ossVersion: 3.9.0
+ossDocsVersion: "3.9"
+ossChartVersion: 8.9.0
+OSSproductName: "Emissary-ingress"
+OSSproductNamePlural: "Emissary-ingresses"
+
+# AES (self)
+aesVersion: 3.9.0
+aesDocsVersion: "3.9"
+aesChartVersion: 8.9.0
+AESproductName: "Ambassador Edge Stack"
+AESproductNamePlural: "Ambassador Edge Stacks"
+
+# other products
+qotmVersion: 1.7
+quoteVersion: 0.5.0
+
+# Most recent version from previous major versions
+# This is mostly to ensure that the migration matrix stays up-to-date
+versionTwoX: 2.5.1
+chartVersionTwoX: 7.6.1
+versionOneX: 1.14.4
+chartVersionOneX: 6.9.5
diff --git a/docs/edge-stack/pre-release/about/faq.md b/docs/edge-stack/pre-release/about/faq.md
index 59b1633f6..14528f9a1 100644
--- a/docs/edge-stack/pre-release/about/faq.md
+++ b/docs/edge-stack/pre-release/about/faq.md
@@ -76,3 +76,13 @@ $productName$ is routing by deploying a test service and seeing if the mapping
works. Then, verify that your load balancer is properly routing requests to
$productName$. In general, verifying each network hop between your client and
backend service is critical to finding the source of the problem.
+
+### What is the difference between the v3alpha1 and v1alpha1 CRDs?
+
+There are two different CRD versions supported by $productName$.
+The first are the `getambassador.io/v3alpha1` CRDs which were introduced with
+$productName$ 2.x. These are still supported and are not deprecated. As of $productName$ $version$, the new `gateway.getambassador.io/v1alpha1` CRDs have also been introduced.
+The `v1alpha1` CRDs have not only a new version, but also a new apigoup so that way they can
+be installed alongside the older CRDs without causing any conflicts.
+
+The `v1alpha1` CRDs are only available for the `Filter`, `FilterPolicy`, `WebApplicationFirewall`, and `WebApplicationFirewallPolicy` resources, and are the next generation of the CRDs that $productName$ will support. We are introducing them now to allow users to try them out without needing to stop using the `v3alpha1` CRDs. You can use `v1alpha1` and `v3alpha1` CRDs in the same cluster at the same time, but `FilterPolicies` are not able to reference `Filters` that do not match their CRD version.
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md
new file mode 100644
index 000000000..2e99bc239
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-apikey.md
@@ -0,0 +1,74 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **APIKey Filter** Type (v1alpha1)
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied. For more information about how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `APIKey Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 APIKey Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## APIKey Filter API Reference
+
+To create an APIKey Filter, the `spec.type` must be set to `apikey`, and the `apikey` field must contain the configuration for your
+APIKey Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-apikey-filter"
+ namespace: "example-namespace"
+spec:
+ type: "apikey" # required
+ apikey: APIKeyFilter # required when `type: "apikey"`
+ httpHeader: string # optional, default: `x-api-key`
+ keys: []APIKeyItem # required, min items: 1
+ - secretName: string # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### APIKeyFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `httpHeader` | `string` | The name of the http header where the api-key will be found (always case-insensitive). By default it will use the `x-api-key` header. |
+| `keys` | \[\][APIKeyItem][] | The set of APIKeys that are used to check the whether the incoming request is valid. |
+
+### APIKeyItem
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `secretName` | `string` | Defines how to resolve the values of the keys. Currently the only supported way to resolve a key is via a local secret. APIKeys cannot use shared secrets in a different namespace than the `APIKey Filter` resource. |
+
+**Note about Secret formatting**:
+When supplying secrets to an API Key filter, the keys of the Secret do not matter, but the value of your API Key must be [base64][] encoded.
+
+For example, if you want to create a secret for the API Key value `example-api-key-value`, the secret should look like:
+
+```yaml
+---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apikey-filter-keys
+ type: Opaque
+ data:
+ any-name-you-want: ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+```
+
+You can specify as many API Keys in the Secret as you like.
+
+[APIKeyItem]: #apikeyitem
+[FilterPolicy Resource]: ../filterpolicy
+[base64]: https://en.wikipedia.org/wiki/Base64
+[the v3alpha1 APIKey Filter api reference]: ../../../getambassador/v3alpha1/filter-apikey
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-external.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-external.md
new file mode 100644
index 000000000..42611ee18
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-external.md
@@ -0,0 +1,138 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **External Filter** Type (v1alpha1)
+
+The `External Filter` allows users to provide their own Kubernetes Service speaking the [ext_authz protocol][].
+$productName$ will send a request to this "External Service" that contains a copy of the incoming request. The External Service will then be able
+to examine details of the incoming request, make changes to its headers, and allow or reject it by sending back a response to $productName$.
+The external service is free to perform any logic it likes before responding to $productName$, allowing for custom filtering and
+processing on incoming requests. The `External Filter` may be used along with any of the other Filter types. For more information about
+how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `External Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `External Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 External Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## External Filter API Reference
+
+To create an External Filter, the `spec.type` must be set to `external`, and the `external` field must contain the configuration for your
+external filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-external-filter"
+ namespace: "example-namespace"
+spec:
+ type: "external" # required
+ external: ExternalFilter # required when `type: "external"`
+ protocol: Enum # required
+ authServiceURL: string # required, must be an absolute url
+ statusOnError: int # optional, default: `403`
+ failureModeAllow: bool # optional, default: `false`
+ timeout: Duration # optional, default: `"5s"`
+ httpSettings: HTTPSettings # optional, can only be set when `protocol: "http"`
+ pathPrefix: string # optional
+ allowedRequestHeaders: []string # optional
+ allowedAuthorizationHeaders: []string # optional
+ addLinkerdHeaders: bool # optional, default: `false`
+ grpcSettings: GRPCSettings # optional, can only be set when `protocol: "grpc"`
+ protocolVersion: Enum # optional, default: `"v3"`
+ include_body: IncludeBody # optional
+ maxBytes: int # required, default: `4096`
+ allowPartial: bool # required, default `true`
+ tlsConfig: TLSConfig # optional
+ certificate: TLSSource # required
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ caCertificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### ExternalFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `protocol` | `Enum` (`"http"`/`"grpc"`) | The type of protocol to use when communicating with the External Service. It is recommended to use "grpc" over "http" due to supporting additional capabilities. |
+| `authServiceURL` | `string` | The URL of the service performing the authorization / filtering logic. Must be an absolute URL. |
+| `statusOnError` | `int` | Allows overriding the status code returned when the External Service returns a non 200 response code for `protocol: "http"` or [DeniedHttpResponse][] for `protocol: "grpc"` |
+| `failureModeAllow` | `bool` | Determines what happens when $productName$ cannot communicate with the External Service due to network issues, or the service not being available. By default, the ExternalFilter will reject the request if it is unable to communicate. This can be overriden by setting this setting to `"true"` so that it fails open, allowing the request through to the upstream service. |
+| `timeout` | [Duration][] | The amount of time $productName$ will wait before erring on a timeout. **Note**: this value cannot be larger than the overall Auth Service timeout that is configured in Envoy or else it would effectively not have any timeout. |
+| `httpSettings` | [HTTPSettings][] | Settings specific to the http protocol. This can only be set when `protocol: "http"`. |
+| `grpcSettings` | [GRPCSettings][] | Settings specific to the grpc protocol. This can only be set when `protocol: "grpc"`. |
+| `include_body` | [IncludeBody][] | Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service. |
+| `tlsConfig` | [TLSConfig][] | Configures tls settings between $productName$ and the configured AuthService |
+
+### Duration
+
+**Appears On**: [ExternalFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### HTTPSettings
+
+**Appears On**:
+Settings specific to the http protocol. This can only be set when `protocol: "http"`.
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `pathPrefix` | `string` | Value that gets appended to the path of the downsteam request. Nothing is appended when this field is omitted |
+| `allowedRequestHeaders` | `[]string` | A list of headers from the downstream request that will be passed along as headers in the request to the external service. This includes metadata sent from Envoy to the EdgeStack Auth Service. By default, the following list of headers are passed through: `authorization`,`cookie`,`from`,`proxy-authorization`, `user-agent`, `x-forwarded-for`, `x-forwarded-host`, `x-forwarded-proto`. |
+| `allowedAuthorizationHeaders` | `[]string` | Headers from the External Service that will be added to the request to the upstream service. By default, the following headers are passed to the upstream service: `location`,`authorization`,`proxy-authenticate`,`set-cookie`,`www-authenticate`. Any additional headers that are needed should be added and are case-insenstive. |
+| `addLinkerdHeaders` | `bool` | When set to `true`, injects the `l5d-dst-override` header set to hostname and port of the external service which is used by [LinkerD][] when proxying through the Service Mesh. |
+
+### GRPCSettings
+
+**Appears On**: [ExternalFilter][]
+Settings specific to the http protocol. This can only be set when `protocol: "grpc"`.
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `protocolVersion` | `Enum` (`"v3"`) | Indicates the version of the transport protocol that the External Filter is using. This is only applicable to External Filters using `protocol: "grpc"`. Currently the only supported version is `"v3"`, so this field exists to provide compatability for future verions of ext_authz. |
+
+### IncludeBody
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `maxBytes` | `int` | Sets the number of bytes of the request body to buffer over to the External Service |
+| `allowPartial` | `bool` | Indicates whether the included body can be a partially buffered body or if the complete buffered body is expected. If not partial then a 413 http error is returned by Envoy. |
+
+### TLSConfig
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|-----------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `certificate.fromSecret` | SecretReference | Configures $productName$ to use the provided certificate to present to the server when connecting. Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret][] that contains the private key and public certificate that will be presented to the AuthService. Secret namespace defaults to Filter namespace if not set |
+| `caCertificate.fromSecret` | SecretReference | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret][] that contains the `tls.crt` key with the CA Certificate. Secret namespace defaults to Filter namespace if not set |
+
+[Duration]: #duration
+[HTTPSettings]: #httpsettings
+[ExternalFilter]: #externalfilter
+[GRPCSettings]: #grpcsettings
+[IncludeBody]: #includebody
+[TLSConfig]: #tlsconfig
+[the v3alpha1 External Filter api reference]: ../../../getambassador/v3alpha1/filter-external
+[ext_authz protocol]: ../../../../topics/running/services/ext-authz
+[FilterPolicy Resource]: ../filterpolicy
+[LinkerD]: https://linkerd.io/
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[DeniedHttpResponse]: https://github.com/envoyproxy/envoy/blob/1230c6cfba3791e4544b4ca23cacdbfc20a6fbaa/api/envoy/service/auth/v3/external_auth.proto
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md
new file mode 100644
index 000000000..34a4d7c07
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-jwt.md
@@ -0,0 +1,174 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **JWT Filter** Type (v1alpha1)
+
+The `JWT Filter` performs JWT validation on a [bearer token][] present in the HTTP header. If the bearer token JWT doesn't validate,
+or has insufficient scope, an RFC 6750-complaint error response with a `www-authenticate` header is returned. The list of acceptable
+signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in the `Filter` configuration. Only RSA and `none`
+algorithms are supported.
+
+
+
+This doc is an overview of all the fields on the `JWT Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `JWT Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 JWT Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## JWT Filter API Reference
+
+To create a JWT Filter, the `spec.type` must be set to `jwt`, and the `jwt` field must contain the configuration for your
+JWT Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-jwt-filter"
+ namespace: "example-namespace"
+spec:
+ type: "jwt" # required
+ jwt: JWTFilter # required when `type: "jwt"`
+ jwksURI: string # optional, required unless `validAlgorithms: ["none"]`
+ validAlgorithms: []Enum # optional, default is all supported algos except for `"none"`
+ audience: string # optional (unless requireAudience: `true`)
+ requireAudience: bool # optional, default: `false`
+ issuer: string # optional (unless requireIssuer: `true`)
+ requireIssuer: bool # optional, default: `false`
+ requireExpiresAt: bool # optional, default: `false`
+ leewayForExpiresAt: Duration # optional
+ requireNotBefore: bool # optional, default: `false`
+ leewayForNotBefore: Duration # optoinal
+ requireIssuedAt: bool # optional, default: `false`
+ leewayForIssuedAt: Duration # optional
+ injectRequestHeaders: []AddHeaderTemplate # optional
+ - name: string # required
+ value: string (GoLang Template) # required
+ maxStale: Duration # optional
+ insecureTLS: bool # optional, default: `false`
+ renegotiateTLS: Enum # optional, default: `"never"`
+ errorResponse: CustomErrorResponse # optional
+ realm: string # optional, default is {name}.{namespace} of the JWT Filter
+ bodyTemplate: string (GoLang Template) # optional
+ headers: []AddHeaderTemplate # optional, max 16 items
+ - name: string # required
+ value: string (GoLang Template) # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### JWTFilter
+
+| **Field** | **Type** | **Description** |
+|------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+ `jwksURI` | `string` | A URI that returns the JWK Set per RFC 7517. This is required unless validAlgorithms=["none"], in that case verifying the signature of the token is disabled. This is considered unsafe and is discouraged when receiving tokens from untrusted sources. |
+ `validAlgorithms` | \[\][ValidAlgorithms][](`Enum`) | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+ `audience` | `string` | Identifies the recipient that the JWT is intended for and will be used to validate the provided token is intended for the configured audience. If not provided then `aud` claim on incoming token is not validated and will be considered valid. If `aud` is unset on the token by default it will be considered valid even if it doesn't match the audience value. To enforce that a token has the aud claim, then set `requireAudience: true`. |
+ `requireAudience` | `bool` | Modifies the validation behavior for when the audience claim (aud) is unset on the incoming token. `false` (default) => if aud claim is unset then claim is considered valid. `true` => if aud claim is unset then claim/token are invalid |
+ `issuer` | `string` | Identifies the expected AuthorizationServer that isssued the token. If not provided then the issuer claim will not be validated. If `issuer` is unset on the token by default it will be considered valid even if it doesn't match the expected issuer value. To enforce that a token has the issuer claim, then set `requireIssuer: true`. |
+ `requireIssuer` | `bool` | Modifies the validation behavior for when the issuer claim (iss) is unset on the incoming token. `false` (default) => if aud claim is unset on incoming token then claim is considered valid `true` => if exp claim is unset then claim is invalid |
+ `requireExpiresAt` | `bool` | Modifies the validation behavior for when the expiresAt claim (exp) is unset on the incoming token. `false` (default) => if exp claim is unset on incoming token then claim is valid `true` => if exp claim is unset then claim/token are invalid |
+ `leewayForExpiresAt` | [Duration][] | Allows token expired by this much to still be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `requireNotBefore` | `bool` | Modifies the validation behavior for when the not before time claim (nbf) is unset on the incoming token. `false` (default) => if `nbf` claim is unset on incoming token then claim is valid `true` => if `nbf` claim is unset then claim/token are invalid |
+ `leewayForNotBefore` | [Duration][] | Allows tokens that shouldn't be used until this much in the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `requireIssuedAt` | `bool` | Modifies the validation behavior for when the issuedAt claim (iat) is unset on the incoming token. `false` (default) => if `iat` claim is unset on incoming token then claim is valid `true` => if `iat` claim is unset then claim/token are invalid |
+ `leewayForIssuedAt` | [Duration][] | Allows tokens issued by this much into the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+ `injectRequestHeaders` | \[\][AddHeaderTemplate][] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream request headers as values. For example, attaching user email claim to a header from the token. |
+ `maxStale` | [Duration][] | Sets the duration that JWKs keys and OIDC discovery responses will be cached, ignoring any caching headers when configured |
+ `insecureTLS` | `bool` | Disables TLS verification for cases when jwksURI begins with `https://`. |
+ `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the JWTFilter will renegotiateTLS with the `jwksURI` server and if so what supported method of renegotiation will be used. |
+ `errorResponse` | [CustomErrorResponse][] | Allows setting a custom Response to the downstream client when an invalid JWT is received. |
+
+### Duration
+
+**Appears On**: [JWTFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ValidAlgorithms
+
+**Appears On**: [JWTFilter][]
+The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not
+in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP,
+as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided
+to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected.
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+
+### AddHeaderTemplate
+
+**Appears On**: [JWTFilter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream
+request headers as values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+`value` is the value of the header to set and is evaluated as a special GoLang Template.
+This allows the header value to be set based on the JWT value. The value is specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.token.Raw` → The raw JWT (`string`)
+- `.token.Header` → The JWT header, as parsed JSON (`map[string]interface{}`)
+- `.token.Claims` → The JWT claims, as parsed JSON (`map[string]interface{}`)
+- `.token.Signature` → The token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+Also available to the template are [the standard functions available to Go text/templates][], as well as:
+
+- a `hasKey` function that takes the a string-indexed map as arg1, and returns whether it contains the key arg2. (This is the same as [the Sprig function of the same name][].)
+- a `doNotSet` function that causes the result of the template to be discarded, and the header field to not be adjusted. This is useful for only conditionally setting a header field; rather than setting it to an empty string or `""`. Note that this does not unset an existing header field of the same name and could be a potential security vulnerability depending on how this is used if an untrusted client spoofs these headers.
+
+
+ Any headers listed will override (not append to) the original request header with that name.
+
+
+### CustomErrorResponse
+
+**Appears On**: [JWTFilter][]
+Allows setting a custom Response to the downstream client when an invalid JWT is received.
+
+| **Field** | **Type** | **Description** |
+|------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `realm` | `string` | Indicates the scope of protection or the application that is checking the token. By default, this is set to the fully qualified name of the `JWT Filter` as `"{name}.{namespace}"` to identify which filter rejected the error. This can be overriden to provide more relevant information to end-users. |
+| `bodyTemplate` | `string` (GoLang Template) | Golang `text/template` string that will be evaluated and used to build the format returned. |
+| `headers` | \[\][AddHeaderTemplate][] | Allows providing additional http response headers for the error response. The current maximum is 16 headers, which aligns with the Gateway-API and modified headers on HTTPRoutes. |
+
+`bodyTemplate` specifies body of the error response returned to the downstream client; specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.status_code` → The HTTP status code to be returned (`int`)
+- `.httpStatus` → An alias for .status_code (`int`, hidden from `{{ . | json "" }}`)
+- `.message` → The error message (`string`)
+- `.error` → The raw Go error object that generated .message (`error`, hidden from `{{ . | json "" }}`)
+- `.error.ValidationError` → The JWT validation error, will be nil if the error is not purely JWT validation (`jwt.ValidationError` insufficient scope, malformed or missing Authorization header)
+- `.request_id` → The Envoy request ID, for correlation (`string`, hidden from `{{ . | json "" }}` unless `.status_code` is in the `5xx` range)
+- `.requestId` → An alias for .request_id (`string`, hidden from `{{ . | json "" }}`)
+
+Also availabe to the template are [the standard functions available to Go text/templates][], as well as:
+
+- A `json` function that formats arg2 as JSON, using the arg1 string as the starting indentation. For example, the template `{{ json "indent>" "value" }}` would yield the string `indent>"value"`.
+
+[JWTFilter]: #jwtfilter
+[ValidAlgorithms]: #validalgorithms
+[AddHeaderTemplate]: #addheadertemplate
+[CustomErrorResponse]: #customerrorresponse
+[Duration]: #duration
+[the v3alpha1 JWT Filter api reference]: ../../../getambassador/v3alpha1/filter-jwt
+[bearer token]: https://datatracker.ietf.org/doc/html/rfc6750
+[Go text/template string]: https://pkg.go.dev/text/template
+[the standard functions available to Go text/templates]: https://pkg.go.dev/text/template#hdr-Functions
+[the Sprig function of the same name]: https://masterminds.github.io/sprig/dicts.html#haskey
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md
new file mode 100644
index 000000000..26ba7d496
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-oauth2.md
@@ -0,0 +1,341 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **OAuth2 Filter** Type (v1alpha1)
+
+The OAuth2 Filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+
+
+
+This doc is an overview of all the fields on the `OAuth2 Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `OAuth2 Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 OAuth2 Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## OAuth2 Filter API Reference
+
+To create an OAuth2 Filter, the `spec.type` must be set to `oauth2`, and the `oauth2` field must contain the configuration for your
+OAuth2 filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-oauth2-filter"
+ namespace: "example-namespace"
+spec:
+ type: "oauth2" # required
+ oauth2: OAuth2Filter # required when `type: "oauth2"`
+ authorizationURL: string # required, must be an absolute url
+ expirationSafetyMargin: Duration # optional
+ injectRequestHeaders: []AddHeaderTemplate # optional
+ - name: string # required
+ value: string (GoLang Template) # required
+ allowMalformedAccessToken: bool # optional, default: `false`
+ accessTokenValidation: Enum # optional, default: `"auto"`
+ accessTokenJWTFilter: JWTFilterReference # optional
+ name: string # required
+ namespace: string # optional
+ inheritScopeArgument: bool # optional, default: `false`
+ stripInheritedScope: bool # optional, default: `false`
+ arguments: JWTArguments # optional
+ scope: []string # optional
+ clientAuthentication: ClientAuthentication # optional
+ method: Enum # optional, default: `"HeaderPassword"`
+ jwtAssertion: JWTAssertion # optional
+ setClientID: bool # optional, default: `false`
+ audience: string # optional
+ signingMethod: Enum # optional, default: `RS256`
+ lifetime: Duration # optional, default: `"1m`
+ setNBF: bool # optional, default: `false`
+ nbfSafetyMargin: Duration # optional
+ setIAT: bool # optional, default: `false`
+ otherClaims: []byte # optional, default: `{}`
+ otherHeaderParameters: []byte # optional, default: `{}`
+ grantType: Enum # required
+ authorizationCodeSettings: AuthorizationCodeSettings # optional, used when `grantType: "AuthorizationCode"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+ name: string # optional
+ namespace: string # optional
+ maxStale: Duration # optional
+ insecureTLS: bool # optional, default: `false`
+ renegotiateTLS: Enum # optional, default: `"never"`
+ protectedOrigins: []Origin # required, min items: 1, max items: 16
+ - origin: string # required, must be an absolute URL, max length: 255
+ includeSubdomains: bool # optional, defualt: `false`
+ allowedInternalOrigins: []string # optional, max items: 16
+ resourceOwnerSettings: ResourceOwnerSettings # optoinal, used when `grantType: "ResourceOwnder"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+ passwordSettings: PasswordSettings # optional, used when `grantType: "Password"`
+ clientID: string # required
+ clientSecret: string # optional
+ clientSecretRef: SecretReference # optional
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### OAuth2Filter
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `authorizationURL` | `string` | Identity Provider Issuer URL which hosts the OpenID provider well-known configurartion. The URL must be an absolute URL. Per [OpenID Connect Discovery 1.0][] the configuration must be provided in a json document at the path `/.well-known/openid-configuration`. This is used by the OAuth2 Filter for determining things like the AuthorizationEndpoint, TokenEndpoint, JWKs endpint, etc... |
+| `expirationSafetyMargin` | [Duration][] | Sets a buffer to check if the Token is expired or is going to expire within the safety margin. This is to ensure the application has enough time to reauthenticate to adjust for clock skew and network latency. By default, no safety margin is added. If a token is received with an expiration less than this field, then the token is considered to already be expired. |
+| `injectRequestHeaders` | \[\][][AddHeaderTemplate] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token. |
+| `allowMalformedAccessToken` | `bool` | Allows any access token even if they are not RFC 6750-compliant. |
+| `accessTokenValidation` | `Enum`(`"auto"`,`"jwt"`,`"userinfo"`) | Sets the method used for validating an AccessToken. |
+| `accessTokenJWTFilter` | [JWTFilterReference][] | Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes |
+| `clientAuthentication` | [ClientAuthentication][] | Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password. |
+| `grantType` | `Enum`(`"AuthorizationCode"`,`"ClientCredentials"`,`"Password"`,`"ResourceOwner"`) | Sets the Authorization Flow that the filter will use to authenticate the incoming request. |
+| `authorizationCodeSettings` | [AuthorizationCodeSettings][] | Specific settings that configure the `AuthorizationCode` grant type. |
+| `resourceOwnerSettings` | [ResourceOwnerSettings][] | Specific settings that configure the `ResourceOwner` grant type. |
+| `passwordSettings` | [PasswordSettings][] | Specific settings that configure the `Password` grant type. |
+
+**`grantType` options**:
+
+- `"AuthorizationCode"`: Authenticate by redirecting to a login page served by the identity provider.
+- `"Password"`: Authenticate by requiring `X-Ambassador-Username` and `X-Ambassador-Password` on all incoming requests, and use them to authenticate with the identity provider using the OAuth2 Resource Owner Password Credentials grant type.
+- `"ClientCredentials"`: Authenticate by requiring that the incoming HTTP request include as headers the credentials for Ambassador to use to authenticate to the identity provider.
+ - The type of credentials needing to be submitted depends on the `clientAuthentication.method` (below):
+ - For `"HeaderPassword"` and `"BodyPassword"`, the headers `X-Ambassador-Client-ID` and `X-Ambassador-Client-Secret` must be set.
+ - For `"JWTAssertion"`, the `X-Ambassador-Client-Assertion` header must be set to a JWT that is signed by your client secret, and conforms with the requirements in RFC 7521 section 5.2 and RFC 7523 section 3, as well as any additional specified by your identity provider.
+
+**`accessTokenValidation` options**:
+
+- `"jwt"`: Validates the Access Token as a JWT.
+
+ - By default: It accepts the RS256, RS384, or RS512 signature algorithms, and validates the signature against the JWKS from
+OIDC Discovery. It then validates the `exp`, `iat`, `nbf`, `iss` (with the Issuer from OIDC Discovery), and `scope` claims: if present,
+none of the scope values are required to be present. This relies on the identity provider using non-encrypted signed JWTs as
+Access Tokens, and configuring the signing appropriately
+ - This behavior can be modified by delegating to [JWT Filter][] with `accessTokenJWTFilter`:
+
+- `"userinfo"`: Validates the access token by polling the OIDC UserInfo Endpoint. This means that $productName$ must initiate
+an HTTP request to the identity provider for each authorized request to a protected resource. This performs poorly,
+but functions properly with a wider range of identity providers. It is not valid to set `accessTokenJWTFilter` if
+`accessTokenValidation`: `userinfo`.
+
+- `"auto"` attempts to do `"jwt"` validation if any of these conditions are true:
+ - `accessTokenJWTFilter` is set
+ - `grantType` is `"ClientCredentials"`
+ - the Access Token parses as a JWT and the signature is valid,
+ - If none of the above conditions are satisfied, it falls back to `"userinfo"` validation.
+
+### Duration
+
+**Appears on**: [Oauth2Filter][], [JWTAssertion][], [AuthorizationCodeSettings][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### AddHeaderTemplate
+
+**Appears On**: [OAuth2Filter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+The header value can be set based on the JWT value. If an `OAuth2 Filter` is chained with a [JWT filter][] with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the `OAuth2 Filter` will populate the value. The value is specified as a [Go text/template][] string, with the following data made available to it:
+
+- `.token.Raw` → The access token raw JWT (`string`)
+- `.token.Header` → The access token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.token.Claims` → The access token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.token.Signature` → The access token signature (`string`)
+- `.idToken.Raw` → The raw id token JWT (`string`)
+- `.idToken.Header` → The id token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Claims` → The id token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Signature` → The id token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+### JWTFilterReference
+
+**Appears On**: [OAuth2Filter][]
+Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes
+
+| **Field** | **Type** | **Description** |
+|------------------------|-------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `namespace` | `string` | Namespace of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `inheritScopeArgument`:| `bool` | Will use the same scope as set on the FilterPolicy OAuth2Arguments. If the JWTFilter sets a scope as well then the union of the two will be used. |
+| `stripInheritedScope` | `bool` | Determines whether or not to santized a scope that is formatted as an URI and was inherited from the FilterPolicy OAuth2Arguments. This will be done prior to passing it along to the referenced JWTFilter. This requires that InheritScopeArgument is true. |
+| `arguments` | [JWTArguments][] | Defines the input arguments that can be set for a JWTFilter. |
+
+### JWTArguments
+
+**Appears On**: [JWTFilterReference][]
+Defines the input arguments that can be set for a JWTFilter.
+
+| **Field** | **Type** | **Description** |
+|------------|-------------|---------------------------------------------------------------------------------------------------------|
+| `scope` | `[]string` | A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope. |
+
+**Some notes about `scope`**:
+
+- If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed.
+- If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+- As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+- The ordering of scope values does not matter, and is ignored.
+
+
+### ClientAuthentication
+
+**Appears On**: [OAuth2Filter][]
+Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password.
+
+| **Field** | **Type** | **Description** |
+|----------------|----------------------------------|---------------------------------------------------------------------------------------------------------|
+| `method` | `Enum`(`"HeaderPassword"`,`"BodyPassword"`,`"JWTAssertion"`) | Defines the type of client authentication that will be used |
+| `jwtAssertion` | [JWTAssertion][] | This field is only used when `method: "JWTAssertion"`. Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow. |
+
+`method` options:
+
+- `"HeaderPassword"`: Treat the client secret as a password, and pack that in to an HTTP header for HTTP Basic authentication.
+- `"BodyPassword"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+- `"JWTAssertion"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+
+### JWTAssertion
+
+**Appears On**: [ClientAuthentication][]
+Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|-------------------------|---------------------------------------------------------------------------------------------------------|
+| `setClientID` | `bool` | Whether to set the Client ID as an HTTP parameter; setting it as an HTTP parameter is optional (per RFC 7521 §4.2) because the Client ID is also contained in the JWT itself, but some identity providers document that they require it to also be set as an HTTP parameter anyway. |
+| `audience` | `string` | This field is ignored when `grantType: "ClientCredentials"`. The audience your IDP requires for authentication. If not set then the default will be to use the token endpoint from the OIDC discovery document. |
+| `signingMethod` | [ValidAlgorithms][] | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `lifetime` | [Duration][] | This field is ignored when `grantType: "ClientCredentials"`. The lifetime of the generated JWT; just enough time for the request to the identity provider to complete (plus possibly an extra allowance for clock skew). |
+| `setNBF` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "nbf" ("Not Before") claim in the generated JWT. |
+| `nbfSafetyMargin` | [Duration][] | This field is only used when `setNBF: true` The safety margin to build-in to the "nbf" claim, to allow for clock skew between ambassador and the identity provider. |
+| `setIAT` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "iat" ("Issued At") claim in the generated JWT. |
+| `otherClaims` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Key/value pairs that will be add to the JWT sent for client Auth to the Identity Provider |
+| `otherHeaderParameters` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Any extra JWT header parameters to include in the generated JWT non-standard claims to include in the generated JWT; only the "typ" and "alg" header parameters are set by default. |
+
+### ValidAlgorithms
+
+**Appears On**: [JWTAssertion][]
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+ - The secret must be a PEM-encoded Eliptic Curve private key
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+ - The secret is a raw string of bytes; it can contain anything
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+ - The secret must be a PEM-encoded RSA private key
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+ - The secret must be a PEM-encoded RSA private key
+
+### AuthorizationCodeSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `AuthorizationCode` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. The Kubernetes Secret must of the `generic` type, with the value stored under the key `oauth2-client-secret` |
+| `maxStale` | [Duration][] | How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also ignores the `no-store` and `no-cache` Cache-Control directives on responses. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Note that if you MUST set `maxStale` as a consistent value on each `Filter` resource to get predictable caching behavior. |
+| `insecureTLS` | `bool` | Tells the $productName$ to skip verifying the IdentityProvider server when communicating with the various endpoints. This is typically needed when using an IdentityProvider configured with self-signed certs. |
+| `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the OAuth2 Filter will renegotiateTLS with the iDP server and if so what supported method of renegotiation will be used. |
+| `protectedOrigins` | \[\][Origin][] | This field is only used when `grantType: "AuthorizationCode"`. List of origins (domains) that the OAuth2 Filter is configured to protect. Setting multiple origins allows for protecting multiple domains using the same Session and Token that is retrieved from the Identity Provider. When setting multiple protected origins, the first origin will be used for the final redirect to the IdentityProvider therefore the identity provider needs to be configured to allow redirects from that origin. However, it is recommended that all protected origins are registered with the IdentityProvider because this is subject to change in the future. Only the scheme `(https://)` and authority `(example.com:1234)` parts are used; the path part of the URL is ignored. You will need to register each origin in `protectedOrigins` as an authorized callback endpoint with your identity provider. The URL will look like `{{ORIGIN}}/.ambassador/oauth2/redirection-endpoint`. |
+
+> **Note**: If you provide more than one protectedOrigin, all share the same authentication system, so that logging into one origin logs you into all origins; to have multiple domains that have separate logins, use separate `Filters`.
+
+### SecretReference
+
+**Appears On**: [AuthorizationCodeSettings][], [PasswordSettings][], [ResourceOwnerSettings][]
+A reference to a [Kubernetes Secret][].
+
+| **Field** | **Type** | **Description** |
+|-------------|------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the Kubernetes Secret being referenced. |
+| `namespace` | `string` | Namespace of the Kubernetes Secret being referenced. |
+
+### Origin
+
+**Appears On**: [AuthorizationCodeSettings][]
+A domain that the OAuth2 Filter is configured to protect. It is recommended that all protected origins are registered with the IdentityProvider because this is subject to change in the future.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|------------|---------------------------------------------------------------------------------------------------------|
+| `origin` | `string` | The absolute URL (schema://hostname) that is protected by the OAuth2 Filter |
+| `includeSubdomains` | `bool` | Enables protecting sub-domains of the domain identified in the Origin field. Example, when `Origin=https://example.com` then the subdomain of `https://app.example.com` would be watched. |
+| `allowedInternalOrigins` | `[]string` | Indentifies a list of allowed internal origins that were set by a downstream proxy via a host header rewrite. The origins identified in this list ensures the request is allowed and will ensure it redirects correctly to the upstream origin. For example, a downstream client will communicate with an origin of `https://example.com` but then an internal proxy will do a rewrite so that the host header received by Edge Stack is `http://example.internal`. |
+
+**Note about `allowedInternalOrigins`**: This field is primarily used to allow you to tell $productName$ that there is another gateway
+in front of $productName$ that rewrites the Host header, so that on the internal network between that gateway and $productName$, the
+origin appears to be `allowedInternalOrigins` instead of `origin`. As a special-case the scheme and/or authority of the `allowedInternalOrigins`
+may be `"*"`, which matches any scheme or any domain respectively.
+Using `"*"` is most useful in configurations with exactly one protected origin; in such a configuration, $productName$ doesn't need
+to know what the origin looks like on the internal network, just that a gateway in front of $productName$ is rewriting it.
+It is invalid to use `"*"` with `includeSubdomains: true`.
+
+For example, if you have a gateway in front of $productName$ handling traffic for `myservice.example.com`, terminating TLS and routing
+that traffic to Ambassador with the name `ambassador.internal`, you might write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - http://ambassador.internal
+```
+
+or, to avoid being fragile to renaming ambassador.internal to something else, since there are not multiple origins that the `Filter` must
+distinguish between, you could instead write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - "*://*"
+```
+
+### ResourceOwnerSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `ResourceOwner` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. |
+
+### PasswordSettings
+
+**Appears On**: [OAuth2Filter][]
+Specific settings that configure the `Password` grant type.
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|---------------------------------------------------------------------------------------------------------|
+| `clientID` | `string` | The ID registered with the IdentityProvider for the client. |
+| `clientSecret` | `string` | The secret registered with the IdentityProvider for the client. |
+| `clientSecretRef` | [SecretReference][] | Reference to a [Kubernetes Secret][] within the cluster that contains the secret registered with the IdentityProvider for the client. |
+
+[AddHeaderTemplate]: #addheadertemplate
+[Oauth2Filter]: #oauth2filter
+[JWTFilterReference]: #jwtfilterreference
+[ClientAuthentication]: #jwtfilterreference
+[AuthorizationCodeSettings]: #authorizationcodesettings
+[ResourceOwnerSettings]: #resourceownersettings
+[PasswordSettings]: #passwordsettings
+[JWTArguments]: #jwtarguments
+[JWTAssertion]: #jwtassertion
+[ValidAlgorithms]: #validalgorithms
+[SecretReference]: #secretreference
+[Origin]: #origin
+[Duration]: #duration
+[JWT Filter]: ../filter-jwt
+[the v3alpha1 OAuth2 Filter api reference]: ../../../getambassador/v3alpha1/filter-oauth2
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
+[Kubernetes secret]: https://kubernetes.io/docs/concepts/configuration/secret/
+[OpenID Connect Discovery 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md
new file mode 100644
index 000000000..7dddf64ff
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter-plugin.md
@@ -0,0 +1,42 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Plugin Filter** Type (v1alpha1)
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled into a .so file,
+which you load into the Envoy Proxy container at `/etc/ambassador-plugins/${NAME}.so`. For more information about how requests are
+matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `Plugin Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 Plugin Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+## Plugin Filter API Reference
+
+To create a Plugin Filter, the `spec.type` must be set to `plugin`, and the `plugin` field must contain the configuration for your Plugin Filter.
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-plugin-filter"
+ namespace: "example-namespace"
+spec:
+ type: "plugin" # required
+ plugin: PluginFilter # required when `type: "plugin"`
+ name: string # required
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+`name`: Indicates the compiled binaries name excluding the extension for the Plugin.
+Envoy Proxy will look for the .so file in the `/etc/ambassador-plugins` directory.
+For example, if `name: "example-plugin"` the .so file should be available at
+`"/etc/ambassador-plugins/example-plugin.so"` on the Envoy Proxy container.
+
+[FilterPolicy Resource]: ../filterpolicy
+[the v3alpha1 Plugin Filter api reference]: ../../../getambassador/v3alpha1/filter-plugin
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter.md
new file mode 100644
index 000000000..91ff36574
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filter.md
@@ -0,0 +1,78 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **Filter** Resource (v1alpha1)
+
+The `Filter` custom resource works in conjunction with the [FilterPolicy custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending them to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests, such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute against those requests. Filters are largely used to add built-in authentication and security, but
+$productName$ also supports developing custom filters to add your own processing and logic.
+
+
+
+This doc is an overview of all the fields on the `Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `Filter` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 Filter api reference][].
+
+
+ v1alpha1Filters can only be referenced from v1alpha1FilterPolicies.
+
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## Filter API Reference
+
+Filtering is configured using `Filter` custom resources. The body of the resource `spec` depends on the filter type:
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: Filter
+metadata:
+ name: "example-filter"
+ namespace: "example-namespace"
+spec:
+ type: Enum # required
+ jwt: JWTFilter # optional, required when `type: "jwt"`
+ oauth2: OAuth2Filter # optional, required when `type: "oauth2"`
+ apikey: APIKeyFilter # optional, required when `type: "apikey"`
+ external: ExternalFilter # optional, required when `type: "external"`
+ plugin: PluginFilter # optional, required when `type: "plugin"`
+status: []metav1.Condition # field managed by controller, max items: 8
+```
+
+### FilterSpec
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum` (`"jwt"`/`"oauth2"`/`"apikey"`/`"external"`/`"plugin"`) | Required field that identifies the type of the Filter that is configured to be executed on a request. |
+| `jwt` | [JWTFilter][] | Provides configuration for the JWT Filter type |
+| `oauth2` | [OAuth2Filter][] | Provides configuration for the OAuth2 Filter type |
+| `apikey` | [APIKeyFilter][] | Provides configuration for the APIKey Filter type |
+| `external` | [ExternalFilter][] | Provides configuration for the External Filter type |
+| `plugin` | [PluginFilter][] | Provides configuration for the Plugin Filter type |
+
+### FilterStatus
+
+This field is set automatically by $productName$ to provide info about the status of the `Filter`.
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-------------------------------------------------------------------------------------------------------------------|
+| `conditions` | \[\][metav1.Condition][] | Describes the current conditions of the WebApplicationFirewall, known conditions are `Accepted`;`Ready`;`Rejected` |
+
+
+ The short name for Filter is fil, so you can get filters using kubectl get filter or kubectl get fil.
+
+
+[FilterPolicy custom resource]: ../filterpolicy
+[JWTFilter]: ../filter-jwt
+[PluginFilter]: ../filter-plugin
+[OAuth2Filter]: ../filter-oauth2
+[APIKeyFilter]: ../filter-apikey
+[ExternalFilter]: ../filter-external
+[the v3alpha1 Filter api reference]: ../../../getambassador/v3alpha1/filter
+[metav1.Condition]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md
new file mode 100644
index 000000000..07caacdd4
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/filterpolicy.md
@@ -0,0 +1,183 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **FilterPolicy** Resource (v1alpha1)
+
+The `FilterPolicy` custom resource works in conjunction with the [Filter custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute on those requests.
+
+
+
+This doc is an overview of all the fields on the `FilterPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `gateway.getambassador.io/v1alpha1` version of the `FilterPolicy` resource. For the older `getambassador.io/v3alpha1` resource,
+please see [the v3alpha1 FilterPolicy api reference][].
+
+
+ v1alpha1FilterPolicies can only be reference v1alpha1Filters.
+
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## FilterPolicy API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec: FilterPolicy
+ rules: []FilterPolicyRule # required, min items: 1
+ - host: string # optional, default: `"*"`
+ path: string # optional, default: `"*"`
+ precedence: int # optional
+ filterRefs: []FilterReference # optional, max items: 5
+ - name: string # required
+ namespace: string # optional
+ onDeny: Enum # optional, default: `"break"`
+ onAllow: Enum # optional, default: `"continue"`
+ ifRequestHeader: HTTPHeaderMatch # optional
+ type: Enum # optional, default: `"Exact"`
+ name: string # required
+ value: string # optional, max length: 4096
+ negate: bool # optional, default: `false`
+ arguments: FilterArguments # optional
+ type: Enum # required
+ jwt: JWTArguments # optional, required when `type: "jwt"`
+ scope: []string # optional
+ oauth2: OAuth2Arguments # optional, required when `type: "oauth2"`
+ scope: []string # optional
+ insteadOfRedirect: OAuth2Redirect # optional
+ statusCode: int # optional
+ ifRequestHeader: HTTPHeaderMatch # optional
+ filterRefs: []FilterReference # optional
+ sameSite: Enum # optional
+status: FilterPolicyStatus # field managed by controller
+ conditions: []metav1.Condition # max items: 8
+ rules: []FilterPolicyRuleStatus # max items: 64
+ - index: string
+ host: string
+ path: string
+ conditions: []metav1.Condition
+```
+
+### FilterPolicy
+
+| **Field** | **Type** | **Description** |
+|------------|----------------------------|-----------------------------------------------------------------------------------|
+| `rules` | \[\][FilterPolicyRule][] | Set of matching rules that are checked against incoming request to determine which set of Filter's to apply. If no matches are found then the request is allowed through to the upstream service without executing any Filters. |
+
+### FilterPolicyRule
+
+**Appears on**: [FilterPolicy][]
+Configures matching rules that are checked against incoming request to determine which `Filter` to apply (if any).
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-----------------------------------------------------------------------------------|
+| `host` | `string` | "glob-string" that matches on the `:authority` header of the incoming request. If not set it will match on all incoming requests. |
+| `path` | `string` | "glob-string" that matches on the request path. If not provided then it will match on all incoming requests. |
+| `filterRefs` | \[\][FilterReference][] | List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths. |
+| `precedence` | `int` | Allows forcing a precedence ordering on the rules. By default the rules are evaluated in the order they are in the `FilterPolicy.spec.rules` field. However, multiple FilterPolicy's can be applied to a cluster. To ensure that a specific ordering is enforced then using a precedence is an option. |
+
+### FilterReference
+
+**Appears on**: [FilterPolicyRule][], [OAuth2Redirect][]
+List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths.
+
+| **Field** | **Type** | **Description** |
+|-------------------|-----------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name that identifies the Filter |
+| `namespace` | `string` | Kubernetes namespace that the Filter resides. It must be a RFC 1123 label. Valid values include: `"example"`, Invalid values include: `"example.com"` (`.` is an invalid character). This validation is based off of the [corresponding Kubernetes validation]. |
+| `onDeny` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `onAllow` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not. |
+| `arguments` | [FilterArguments][] | Strongly typed input arguments that can be passed into a Filter on per [FilterReference][] level allowing for different behavior on different Rules. |
+
+### HTTPHeaderMatch
+
+**Appears on**: [FilterPolicyRule][], [OAuth2Redirect][]
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum`(`"Exact"`,`"RegularExpression"`) | The semantics of how HTTP header values should be evaluated |
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of HTTP Header to be matched. If type is RegularExpression then this must be a valid regex with length being at least 1. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### FilterArguments
+
+**Appears on**: [FilterPolicyRule][]
+Strongly typed input arguments that can be passed into a Filter on per [FilterReference][] level allowing for different behavior on different Rules.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------------|-----------------------------------------------------------------------------------|
+| `type` | `Enum` (`"jwt"`/`"oauth2"`) | Identifies the expected type of the arguments that will be passed to the `FilterRef`. This must match the type of the `filterRef` and if it doesn't the `FilterPolicy` `rule` will be considered invalid and a status condition will be updated to indicate the mismatch. |
+| `jwt` | [JWTArguments][] | Defines the input arguments that can be set for an [JWT Filter][] on a per `FilterPolicy` `rule` level. |
+| `oauth2` | [OAuth2Arguments][] | Defines the input arguments that can be set for an [OAuth2 Filter][] on a per `FilterPolicy` `rule` level. |
+
+### JWTArguments
+
+**Appears on**: [FilterArguments][]
+Defines the input arguments that can be set for an [JWT Filter][] on a per `FilterPolicy` `rule` level.
+
+| **Field** | **Type** | **Description** |
+|--------------|-------------|-----------------------------------------------------------------------------------|
+| `scope` | `[]string` | Set of scopes the JWT will be validated against |
+
+### OAuth2Arguments
+
+**Appears on**: [FilterArguments][]
+Defines the input arguments that can be set for an [OAuth2 Filter][] on a per `FilterPolicy` `rule` level.
+
+| **Field** | **Type** | **Description** |
+|---------------------|-------------------------------------------------|-----------------------------------------------------------------------------------|
+| `scope` | `[]string` | Set of scopes the JWT will be validated against |
+| `insteadOfRedirect` | [OAuth2Redirect][] | Allows customizing the behavior of the OAuth2 redirect and whether it will redirect the browser or not. |
+| `sameSite` | `Enum`(`"default"`,`"none"`,`"lax"`,`"strict"`) | Set of options for setting the SameSite attribute on a cookie. [https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00][] for details. |
+
+### OAuth2Redirect
+
+**Appears on**: [OAuth2Arguments][]
+Allows customizing the behavior of the OAuth2 redirect and whether it will redirect the browser or not.
+
+| **Field** | **Type** | **Description** |
+|-------------------|-------------------------|-----------------------------------------------------------------------------------|
+| `statusCode` | `int` | The HTTP status code to be used in response. If filterRef is not set then this will default to a 403 forbidden. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Allows only applying the InsteadOfRedirect logic when a the header matches. |
+| `filterRefs` | \[\][FilterReference][] | List of references to Filter's that will be applied when an OAuth2 session has expired and the user would like to try a secondary authentication mechanism without redircting to the iDP. Nesting an [OAuth2 Filter][] inside of an [OAuth2 Filter][] is not supported. |
+
+### FilterPolicyStatus
+
+Automatically managed by the controller to reflect the state of the `FilterPolicy`
+
+| **Field** | **Type** | **Description** |
+|------------------------|---------------------------|-----------------------------------------------------------------------------------|
+| `conditions` | \[\][metav1.Condition][] | Describes the current condition of the `FilterPolicy`. |
+| `rules` |`[]FilterPolicyRuleStatus` | Describes the status for each unique Rule defined in the `Spec` |
+| `rules.index` | `string` | The zero-based index of the rule within `rules` with the problem to help with identifying the error |
+| `rules.host` | `string` | `host` of the rule with the problem to help with identifying the error
+| `rules.path` | `string` | `path` of the rule with the problem to help with identifying the error |
+| `rules.conditions` | \[\][metav1.Condition][] | Describes the current condition of a specific `rule`. |
+
+[FilterPolicy]: #filterpolicy
+[FilterPolicyRule]: #filterpolicyrule
+[FilterReference]: #filterreference
+[FilterArguments]: #filterarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[OAuth2Redirect]: #oauth2redirect
+[OAuth2Arguments]: #oauth2arguments
+[JWTArguments]: #jwtarguments
+[JWT Filter]: ../filter-jwt
+[OAuth2 Filter]: ../filter-oauth2
+[Filter custom resource]: ../filter
+[the v3alpha1 FilterPolicy api reference]: ../../../getambassador/v3alpha1/filterpolicy
+[corresponding Kubernetes validation]: https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
+[https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00]: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
+[metav1.Condition]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md
new file mode 100644
index 000000000..d2fcf7f35
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall.md
@@ -0,0 +1,84 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **WebApplicationFirewall** Resource (v1alpha1)
+
+The `WebApplicationFirewall` provides the configuration for an instance of a Web Application Firewall, and the
+[WebApplicationFirewallPolicy][] resource configures the matching patterns for when `WebApplicationFirewalls` get executed against requests.
+
+This doc is an overview of all the fields on the `WebApplicationFirewall` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+Tutorials and guides for Web Application Firewalls can be found in the [usage guides section][]
+
+
+ The WebApplicationFirewall resource was introduced more recently than the Filter and FilterPolicy resources, and does not have an older getambassador.io/v3alpha1 CRD version
+
+
+## WebApplicationFirewall API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewall
+metadata:
+ name: "example-waf"
+ namespace: "example-namespace"
+spec:
+ firewallRules: FirewallRules # required, One of configMapRef;file;http must be set below
+ sourceType: Enum # required
+ configMapRef: ConfigMapReference # optional
+ name: string # required
+ namespace: string # required
+ key: string # required
+ file: string # optional
+ http: # optional
+ url: string # required, must be a valid URL.
+ logging: # optional
+ onInterrupt: # required
+ enabled: bool # required
+status: # field managed by controller
+ conditions: []metav1.Condition
+```
+
+### WebApplicationFirewall
+
+| **Field** | **Type** | **Description** |
+|--------------------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `firewallRules` | [FirewallRules][] | Defines the rules to be used for the Web Application Firewall |
+| `logging.onInterrupt.enabled` | `bool` | When enabled, creates additional log lines in the $productName$ pods whenever the `WebApplicationFirewall` interrupts a request. This is in addition to the logging config that is available via the firewall configuration files. |
+
+### FirewallRules
+
+Defines the rules to be used for the Web Application Firewall
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `sourceType` | `Enum`(`"file"`,`"configmap"`,`"http"`) | Identifies which method is being used to load the firewall rules. Value must be one of `configMapRef`;`file`;`http`. The value corresponds to the following fields for configuring the selected method. |
+| `configMapRef` | [ConfigMapReference][] | Defines a reference to a [Kubernetes ConfigMap][] to load firewall rules from. |
+| `file` | `string` | Location of a file on disk to load the firewall rules from. Example: `"/ambassador/firewall/waf.conf"`. Files can be mounted to the $productName$ auth service deployment pods using a `ConfigMap`, or similar approach. |
+| `http.url` | `string` | URL to fetch firewall rules from. If the rules are unable to be downloaded/parsed from the provided url for whatever reason, the requests matched to this `WebApplicationFirewall` will be allowed/denied based on the configuration of the `onError` field. |
+
+### ConfigMapReference
+
+Defines a reference to a [Kubernetes ConfigMap][] to load firewall rules from.
+
+| **Field** | **Type** | **Description** |
+|--------------|------------|-----------------------------------------------|
+| `name` | `string` | Name of the referenced Kuberntes `ConfigMap`. |
+| `namespace` | `string` | Namespace of the referenced Kuberntes `ConfigMap`.|
+| `key` | `string` | The key in the referenced Kuberntes `ConfigMap` to pull the rules data from. |
+
+## Web Application Firewall Usage Guides
+
+The following guides will help you get started using Web Application Firewalls
+
+- [Using Web Application Firewalls][] - Get started using `WebApplicationFirealls` quickly
+- [Rules for Web Application Firewalls][] - Info about creating and configuring firewall rules
+- [Web Application Firewalls in Production][] - Recommendations and info for creating and running `WebApplicationFirewalls` in a production environment
+
+[FirewallRules]: #firewallrules
+[ConfigMapReference]: #configmapreference
+[usage guides section]: #web-application-firewall-usage-guides
+[WebApplicationFirewallPolicy]: ../webapplicationfirewallpolicy
+[Using Web Application Firewalls]: ../../../../howtos/web-application-firewalls
+[Rules for Web Application Firewalls]: ../../../../howtos/web-application-firewalls-config
+[Web Application Firewalls in Production]: ../../../../howtos/web-application-firewalls-in-production
+[Kubernetes ConfigMap]: https://kubernetes.io/docs/concepts/configuration/configmap/
diff --git a/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md
new file mode 100644
index 000000000..f9a8a7fcd
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy.md
@@ -0,0 +1,102 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **WebApplicationFirewallPolicy** Resource (v1alpha1)
+
+The `WebApplicationFirewallPolicy` resource configures the matching patterns for when [WebApplicationFirewalls][] get executed against requests; while the
+`WebApplicationFirewall` resource provides the configuration for an instance of a Web Application Firewall.
+
+This doc is an overview of all the fields on the `WebApplicationFirewallPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+Tutorials and guides for Web Application Firewalls can be found in the [usage guides section][]
+
+
+ The WebApplicationFirewallPolicy resource was introduced more recently than the Filter and FilterPolicy resources, and does not have an older getambassador.io/v3alpha1 CRD version
+
+
+## WebApplicationFirewallPolicy API Reference
+
+```yaml
+---
+apiVersion: gateway.getambassador.io/v1alpha1
+kind: WebApplicationFirewallPolicy
+metadata:
+ name: "example-wafpolicy"
+ namespace: "example-namespace"
+spec:
+ rules: []WafMatchingRule # required
+ - host: string # optional, default: `"*"` (runs on all hosts)
+ path: string # optional, default: `"*"` (runs on all paths)
+ ifRequestHeader: HTTPHeaderMatch # optional
+ type: Enum # optional, default: `"Exact"`
+ name: string # required
+ value: string # optional
+ negate: bool # optional, default: `false`
+ wafRef: # required
+ name: string # required
+ namespace: string # required
+ onError: # optional
+ statusCode: int # required, min: `400`, max: `599`
+ precedence: int # optional
+status: # field managed by controller
+ conditions: []metav1.Condition
+ ruleStatuses:
+ - index: int
+ host: string
+ path: string
+ conditions: []metav1.Condition
+```
+
+### WebApplicationFirewallPolicy Spec
+
+| **Field** | **Type** | **Description** |
+|-----------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `rules` | \[\][WafMatchingRule][] | This object configures matching requests and executes WebApplicationFirewalls on them. Multiple different rules can be supplied in one `WebApplicationFirewallPolicy` instead of multiple separate `WebApplicationFirewallPolicy` resouurces if desired. |
+
+### WafMatchingRule
+
+| **Field** | **Type** | **Description** |
+|----------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `host` | `string` | A "glob-string" that matches on the `:authority` header of the incoming request. If not set, it will match on all incoming requests. |
+| `path` | `string` | A "glob-string" that matches on the request path. If not provided, then it will match on all incoming requests. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request header to determine if the `WebApplicationFirewall` is executed or not. |
+| `wafRef` | [WafReference][] | A reference to a `WebApplicationFirewall` to be applied against the request. |
+| `onError.statusCode` | `int` | Configure a response code to be sent to the downstream client when when a request matches the rule but there is a configuration or runtime error. By default, requests are allowed on error if this field is not configured. This covers runtime errors such as those caused by networking/request parsing as well as configuration errors such as if the `WebApplicationFirewall` that is referenced is misconfigured, cannot be found, or when its configuration cannot be loaded properly. Details about the errors can be found either in the `WebApplicationFirewall` status or container logs. |
+
+### HTTPHeaderMatch
+
+**Appears On**: [WafMatchingRule][]
+Checks if exact or regular expression matches a value in a request header to determine if the `WebApplicationFirewall` is executed or not.
+
+| **Field** | **Type** | **Description** |
+|------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `type` | `Enum`(`"Exact"`,`"RegularExpression"`) | Specifies how to match against the value of the header. Allowed values are `"Exact"`/`"RegularExpression"`. |
+| `name` | `string` | Name of the HTTP Header to be matched. Name matching MUST be case-insensitive. (See [https://tools.ietf.org/html/rfc7230#section-3.2][]) |
+| `value` | `string` | Value of HTTP Header to be matched. If type is `RegularExpression`, then this must be a valid regex with a length of at least 1. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. |
+
+### WafReference
+
+**Appears On**: [WafMatchingRule][]
+A reference to a `WebApplicationFirewall`
+
+| **Field** | **Type** | **Description** |
+|---------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `name` | Name of the `WebApplicationFirewall` being referenced
+| `namespace` | Namespace of the `WebApplicationFirewall`. This field is required. It must be a RFC 1123 label. Valid values include: `"example"`. Invalid values include: `"example.com"` - `"."` is an invalid character. The maximum allowed length is 63 characters, and the regex pattern `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` is used for validation. |
+
+## Web Application Firewall Usage Guides
+
+The following guides will help you get started using Web Application Firewalls
+
+- [Using Web Application Firewalls][]
+- [Rules for Web Application Firewalls][]
+- [Web Application Firewalls in Production][]
+
+[WafReference]: #wafreference
+[HTTPHeaderMatch]: #httpheadermatch
+[WafMatchingRule]: #wafmatchingrule
+[usage guides section]: #web-application-firewall-usage-guides
+[Using Web Application Firewalls]: ../../../../howtos/web-application-firewalls
+[Rules for Web Application Firewalls]: ../../../../howtos/web-application-firewalls-config
+[Web Application Firewalls in Production]: ../../../../howtos/web-application-firewalls-in-production
+[WebApplicationFirewalls]: ../webapplicationfirewall
+[https://tools.ietf.org/html/rfc7230#section-3.2]: https://tools.ietf.org/html/rfc7230#section-3.2
diff --git a/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-apikey.md b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-apikey.md
new file mode 100644
index 000000000..719f2d7a1
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-apikey.md
@@ -0,0 +1,76 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **APIKey Filter** Type (v3alpha1)
+
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied.
+For more information about how requests are matched to `Filter` resources and the order in which `Filters` are executed, please
+refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `APIKey Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `APIKey Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 APIKey Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## APIKey Filter API Reference
+
+To create an APIKey Filter, the `spec.type` must be set to `apikey`, and the `apikey` field must contain the configuration for your
+APIKey Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-apikey-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string
+ APIKey: APIKeyFilter # required
+ httpHeader: string # optional, default: `x-api-key`
+ keys: []APIKeyItem # required, min items: 1
+ - secretName: string # required
+```
+
+### APIKeyFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `httpHeader` | `string` | The name of the http header where the api-key will be found (always case-insensitive). By default it will use the `x-api-key` header. |
+| `keys` | \[\][APIKeyItem][] | The set of APIKeys that are used to check the whether the incoming request is valid. |
+
+### APIKeyItem
+
+| **Field** | **Type** | **Description** |
+|--------------------|---------------------|------------------|
+| `secretName` | `string` | Defines how to resolve the values of the keys. Currently the only supported way to resolve a key is via a local secret. APIKeys cannot use shared secrets in a different namespace than the `APIKey Filter` resource. |
+
+**Note about Secret formatting**:
+When supplying secrets to an API Key filter, the keys of the Secret do not matter, but the value of your API Key must be [base64][] encoded.
+
+For example, if you want to create a secret for the API Key value `example-api-key-value`, the secret should look like:
+
+```yaml
+---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apikey-filter-keys
+ type: Opaque
+ data:
+ any-name-you-want: ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+```
+
+You can specify as many API Keys in the Secret as you like.
+
+[APIKeyItem]: #apikeyitem
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 APIKey Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-apikey
+[base64]: https://en.wikipedia.org/wiki/Base64
diff --git a/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-external.md b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-external.md
new file mode 100644
index 000000000..53b2968f9
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-external.md
@@ -0,0 +1,110 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **External Filter** Type (v3alpha1)
+
+The `External Filter` allows users to provide their own Kubernetes Service speaking the [ext_authz protocol][].
+$productName$ will send a request to this "External Service" that contains a copy of the incoming request. The External Service will then be able
+to examine details of the incoming request, make changes to its headers, and allow or reject it by sending back a response to $productName$.
+The external service is free to perform any logic it likes before responding to $productName$, allowing for custom filtering and
+processing on incoming requests. The `External Filter` may be used along with any of the other Filter types. For more information about
+how requests are matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `External Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `External Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 External Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## External Filter API Reference
+
+To create an External Filter, the `spec.type` must be set to `external`, and the `external` field must contain the configuration for your
+external filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-external-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ External: ExternalFilter # required
+ auth_service: string # required
+ tls: bool # optional, default=true if auth_service starts with https://
+ tlsConfig: TLSConfig # optional
+ certificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ caCertificate: TLSSource # optional
+ fromSecret: SecretReference # required
+ name: string # required
+ namespace: string # optional
+ proto: Enum # optional, default=http
+ timeout_ms: int # optional, default=5000
+ allowed_request_headers: []string # optional
+ allowed_authorization_headers: []string # optional
+ add_linkerd_headers: bool # optional
+ path_prefix: string # optional
+ include_body: IncludeBody # optional
+ max_bytes: int # required
+ allow_partial: bool # required
+ protocol_version: Enum # required
+ status_on_error: # optional
+ code: int # required
+ failure_mode_allow: bool # optional
+
+```
+
+### ExternalFilter
+
+| **Field** | **Type** | **Description** |
+|--------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `auth_service` | `string` | Identifies the external auth service to talk to. The format of this field is `scheme://host:port` where `scheme://` and `:port` are optional. The scheme-part, if present, must be either `http://` or `https://`; if the scheme-part is not present, it behaves as if `http://` is given. The scheme-part influences the default value of the `tls` field and the default value of the port-part. The host-part must be the [namespace-qualified DNS name][] of the service you want to use for authentication. |
+| `tls` | `bool` | Controls whether to use TLS or cleartext when speaking to the external auth service. The default is based on the scheme-part of the `auth_service` |
+| `tlsConfig` | [TLSConfig][] | Configures tls settings between $productName$ and the configured AuthService |
+| `proto` | `Enum` (`"http"`/`"grpc"`) | The type of [ext_authz protocol][] to use when communicating with the External Service. It is recommended to use "grpc" over "http" due to supporting additional capabilities. |
+| `timeout_ms` | `int` | The total maximum duration in milliseconds for the request to the external auth service, before triggering `status_on_error` or `failure_mode_allow` |
+| `allowed_request_headers` | `[]string` | Only applies when `proto: http`. Lists the headers (case-insensitive) that are copied from the incoming request to the request made to the external auth service. In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Cookie`, `From`, `Proxy-Authorization`, `User-Agent`, `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto` |
+| `allowed_authorization_headers` | `[]string` | Only applies when `proto: http`. Lists the headers (case-insensitive) that are copied from the response from the external auth service to the request sent to the upstream backend service (if the external auth service indicates that the request to the upstream backend service should be allowed). In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Location`, `Proxy-Authenticate`, `Set-cookie`, `WWW-Authenticate` |
+| `add_linkerd_headers` | `bool` | Only applies when `proto: http`. When true, in the request to the external auth service, adds an `l5d-dst-override` HTTP header that is set to the hostname and port number of the external auth service |
+| `path_prefix` | `string` | Only applies when `proto: http`. Prepends a string to the request path of the request when sending it to the external auth service. By default this is empty, and nothing is prepended. For example, if the client makes a request to `/foo`, and `path_prefix: /bar`, then the path in the request made to the external auth service will be `/foo/bar` |
+| `include_body` | [IncludeBody][] | Controls how much to buffer the request body to pass to the external auth service, for use cases such as computing an HMAC or request signature. If `include_body` is unset, then the request body is not buffered at all, and an empty body is passed to the external auth service. If include_body is not null, the `max_bytes` and `allow_partial` subfields are required. Unfortunately, in order for `include_body` to function properly, the `AuthService` resource must be edited to have its own `include_body` set with `max_bytes` greater than the largest `max_bytes` used by any `External Filter`, and `allow_partial: true` |
+| `status_on_error.code` | `int` | Controls the status code returned when unable to communicate with external auth service. This is ignored if `failure_mode_allow: true` |
+| `failure_mode_allow` | `bool` | Controls whether to allow or reject requests when there is an error communicating with the external auth service; a value of true allows the request through to the upstream backend service, a value of false returns a `status_on_error.code` response to the client |
+| `protocol_version` | `Enum (v3)` | Only applies when `proto: grpc`. Indicates the version of the transport protocol that the `External Filter` is using. Allowed values are `v3` and `v2`. `protocol_version` was used in previous versions of $productName$ to note the protocol used by the gRPC service for the `External Filter`. $productName$ 3.x is running an updated version of Envoy that has dropped support for the `v2` protocol, so starting in 3.x, if `protocol_version` is not specified, the default value of `v2` will cause an error to be posted and a static response will be returned. Therefore, you must set it to `protocol_version: v3`. If upgrading from a previous version, you will want to set it to `v3` and ensure it is working before upgrading to $productName$ 3.x. The default value for `protocol_version` remains `v2` in the `getambassador.io/v3alpha1` CRD specifications to avoid making breaking changes outside of a CRD version change |
+
+### IncludeBody
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `maxBytes` | `int` | Sets the number of bytes of the request body to buffer over to the External Service |
+| `allowPartial` | `bool` | Indicates whether the included body can be a partially buffered body or if the complete buffered body is expected. If not partial then a `HTTP 413` error is returned by Envoy. |
+
+### TLSConfig
+
+**Appears On**: [ExternalFilter][]
+Configures passing along the request body to the External Service. If not set then a blank body is sent over to the External Service.
+
+| **Field** | **Type** | **Description** |
+|-----------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `certificate.fromSecret` | SecretReference | Configures $productName$ to use the provided certificate to present to the server when connecting. Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret][] that contains the private key and public certificate that will be presented to the AuthService. Secret namespace defaults to Filter namespace if not set |
+| `caCertificate.fromSecret` | SecretReference | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret][] that contains the `tls.crt` key with the CA Certificate. Secret namespace defaults to Filter namespace if not set |
+
+
+[ExternalFilter]: #externalfilter
+[IncludeBody]: #includebody
+[TLSConfig]: #tlsconfig
+[ext_authz protocol]: ../../../../topics/running/services/ext-authz
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 External Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-external
+[Kubernetes Secret]: https://kubernetes.io/docs/concepts/configuration/secret
+[namespace-qualified DNS name]: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services
diff --git a/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-jwt.md b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-jwt.md
new file mode 100644
index 000000000..7bcb69479
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-jwt.md
@@ -0,0 +1,176 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **JWT Filter** Type (v3alpha1)
+
+The `JWT Filter` performs JWT validation on a [bearer token][] present in the HTTP header. If the bearer token JWT doesn't validate,
+or has insufficient scope, an RFC 6750-complaint error response with a `www-authenticate` header is returned. The list of acceptable
+signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in the `Filter` configuration. Only RSA and `none`
+algorithms are supported. For more information about how requests are matched to `Filter` resources and the order in which
+`Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `JWT Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `JWT Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 JWT Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## JWT Filter API Reference
+
+To create a JWT Filter, the `spec.type` must be set to `jwt`, and the `jwt` field must contain the configuration for your
+JWT Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-jwt-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ JWT: JWTFilter # required
+ jwksURI: string # required, (unless the only validAlgorithm is "none")
+ insecureTLS: bool # optional, default=false
+ renegotiateTLS: Enum # optional, default="never"
+ validAlgorithms: []string # optional, default is "all supported algos except for 'none'"
+ audience: string # optional (unless `requireAudience: true`)
+ requireAudience: bool # optional, default=false
+ issuer: string # optional (unless `requireIssuer: true`)
+ requireIssuer: bool # optional, default=false
+ requireExpiresAt: bool # optional, default=false
+ leewayForExpiresAt: Duration # optional
+ requireNotBefore: bool # optional, default=false
+ leewayForNotBefore: Duration # optional
+ requireIssuedAt: bool # optional, default=false
+ leewayForIssuedAt: Duration # optional
+ maxStale: Duration # optional
+ injectRequestHeaders: AddHeaderTemplate # optional
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ errorResponse: ErrorResponse # optional
+ contentType: "string" # deprecated, use 'headers' instead
+ realm: "string" # optional, default="{{.metadata.name}}.{{.metadata.namespace}}"
+ headers: # optional, default=[{name: "Content-Type", value: "application/json"}]
+ - name: "header-name-string" # required
+ value: "go-template-string" # required
+ bodyTemplate: "string" # optional, default=`{{ . | json "" }}`
+```
+
+### JWTFilter
+
+| **Field** | **Type** | **Description** |
+|-------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `jwksURI` | `string` | A URI that returns the JWK Set per RFC 7517. This is required unless validAlgorithms=["none"], in that case verifying the signature of the token is disabled. This is considered unsafe and is discouraged when receiving tokens from untrusted sources. |
+| `validAlgorithms` | \[\][ValidAlgorithms][](`Enum`) | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `audience` | `string` | Identifies the recipient that the JWT is intended for and will be used to validate the provided token is intended for the configured audience. If not provided then `aud` claim on incoming token is not validated and will be considered valid. If `aud` is unset on the token by default it will be considered valid even if it doesn't match the audience value. To enforce that a token has the aud claim, then set `requireAudience: true`. |
+| `requireAudience` | `bool` | Modifies the validation behavior for when the audience claim (aud) is unset on the incoming token. `false` (default) => if aud claim is unset then claim is considered valid. `true` => if aud claim is unset then claim/token are invalid |
+| `issuer` | `string` | Identifies the expected AuthorizationServer that isssued the token. If not provided then the issuer claim will not be validated. If `issuer` is unset on the token by default it will be considered valid even if it doesn't match the expected issuer value. To enforce that a token has the issuer claim, then set `requireIssuer: true`. |
+| `requireIssuer` | `bool` | Modifies the validation behavior for when the issuer claim (iss) is unset on the incoming token. `false` (default) => if aud claim is unset on incoming token then claim is considered valid `true` => if exp claim is unset then claim is invalid |
+| `requireExpiresAt` | `bool` | Modifies the validation behavior for when the expiresAt claim (exp) is unset on the incoming token. `false` (default) => if exp claim is unset on incoming token then claim is valid `true` => if exp claim is unset then claim/token are invalid |
+| `leewayForExpiresAt` | [Duration][] | Allows token expired by this much to still be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `requireNotBefore` | `bool` | Modifies the validation behavior for when the not before time claim (nbf) is unset on the incoming token. `false` (default) => if `nbf` claim is unset on incoming token then claim is valid `true` => if `nbf` claim is unset then claim/token are invalid |
+| `leewayForNotBefore` | [Duration][] | Allows tokens that shouldn't be used until this much in the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `requireIssuedAt` | `bool` | Modifies the validation behavior for when the issuedAt claim (iat) is unset on the incoming token. `false` (default) => if `iat` claim is unset on incoming token then claim is valid `true` => if `iat` claim is unset then claim/token are invalid |
+| `leewayForIssuedAt` | [Duration][] | Allows tokens issued by this much into the future to be considered valid. It is recommend that this is small, so that it only accounts for clock skew and network/application latency. |
+| `injectRequestHeaders` | \[\][AddHeaderTemplate][] | List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream request headers as values. For example, attaching user email claim to a header from the token. |
+| `maxStale` | [Duration][] | Sets the duration that JWKs keys and OIDC discovery responses will be cached, ignoring any caching headers when configured |
+| `insecureTLS` | `bool` | Disables TLS verification for cases when jwksURI begins with `https://`. |
+| `renegotiateTLS` | `Enum` (`never`,`onceAsClient`,`freelyAsClient`) | Sets whether the JWTFilter will renegotiateTLS with the `jwksURI` server and if so what supported method of renegotiation will be used. |
+| `errorResponse` | [CustomErrorResponse][] | Allows setting a custom Response to the downstream client when an invalid JWT is received. |
+
+### Duration
+
+**Appears On**: [JWTFilter][]
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ValidAlgorithms
+
+**Appears On**: [JWTFilter][]
+The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not
+in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP,
+as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided
+to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected.
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+
+### AddHeaderTemplate
+
+**Appears On**: [JWTFilter][]
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token and downstream
+request headers as values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+`value` is the value of the header to set and is evaluated as a special GoLang Template.
+This allows the header value to be set based on the JWT value. The value is specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.token.Raw` → The raw JWT (`string`)
+- `.token.Header` → The JWT header, as parsed JSON (`map[string]interface{}`)
+- `.token.Claims` → The JWT claims, as parsed JSON (`map[string]interface{}`)
+- `.token.Signature` → The token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+Also available to the template are [the standard functions available to Go text/templates][], as well as:
+
+- a `hasKey` function that takes the a string-indexed map as arg1, and returns whether it contains the key arg2. (This is the same as [the Sprig function of the same name][].)
+- a `doNotSet` function that causes the result of the template to be discarded, and the header field to not be adjusted. This is useful for only conditionally setting a header field; rather than setting it to an empty string or `""`. Note that this does not unset an existing header field of the same name and could be a potential security vulnerability depending on how this is used if an untrusted client spoofs these headers.
+
+
+ Any headers listed will override (not append to) the original request header with that name.
+
+
+### CustomErrorResponse
+
+**Appears On**: [JWTFilter][]
+Allows setting a custom Response to the downstream client when an invalid JWT is received.
+
+| **Field** | **Type** | **Description** |
+|------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `realm` | `string` | Indicates the scope of protection or the application that is checking the token. By default, this is set to the fully qualified name of the `JWT Filter` as `"{name}.{namespace}"` to identify which filter rejected the error. This can be overriden to provide more relevant information to end-users. |
+| `bodyTemplate` | `string` (GoLang Template) | Golang `text/template` string that will be evaluated and used to build the format returned. |
+| `headers` | \[\][AddHeaderTemplate][] | Allows providing additional http response headers for the error response. The current maximum is 16 headers, which aligns with the Gateway-API and modified headers on HTTPRoutes. |
+
+`bodyTemplate` specifies body of the error response returned to the downstream client; specified as a [Go text/template string][],
+with the following data made available to it:
+
+- `.status_code` → The HTTP status code to be returned (`int`)
+- `.httpStatus` → An alias for .status_code (`int`, hidden from `{{ . | json "" }}`)
+- `.message` → The error message (`string`)
+- `.error` → The raw Go error object that generated .message (`error`, hidden from `{{ . | json "" }}`)
+- `.error.ValidationError` → The JWT validation error, will be nil if the error is not purely JWT validation (`jwt.ValidationError` insufficient scope, malformed or missing Authorization header)
+- `.request_id` → The Envoy request ID, for correlation (`string`, hidden from `{{ . | json "" }}` unless `.status_code` is in the `5xx` range)
+- `.requestId` → An alias for .request_id (`string`, hidden from `{{ . | json "" }}`)
+
+Also availabe to the template are [the standard functions available to Go text/templates][], as well as:
+
+- A `json` function that formats arg2 as JSON, using the arg1 string as the starting indentation. For example, the template `{{ json "indent>" "value" }}` would yield the string `indent>"value"`.
+
+[JWTFilter]: #jwtfilter
+[ValidAlgorithms]: #validalgorithms
+[AddHeaderTemplate]: #addheadertemplate
+[CustomErrorResponse]: #customerrorresponse
+[Duration]: #duration
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 JWT Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-jwt
+[bearer token]: https://datatracker.ietf.org/doc/html/rfc6750
+[Go text/template string]: https://pkg.go.dev/text/template
+[the standard functions available to Go text/templates]: https://pkg.go.dev/text/template#hdr-Functions
+[the Sprig function of the same name]: https://masterminds.github.io/sprig/dicts.html#haskey
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
diff --git a/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-oauth2.md b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-oauth2.md
new file mode 100644
index 000000000..0b55315a4
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-oauth2.md
@@ -0,0 +1,368 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **OAuth2 Filter** Type (v3alpha1)
+
+The OAuth2 Filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+
+
+
+This doc is an overview of all the fields on the `OAuth2 Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `OAuth2 Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 OAuth2 Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## OAuth2 Filter API Reference
+
+To create an OAuth2 Filter, the `spec.type` must be set to `oauth2`, and the `oauth2` field must contain the configuration for your
+OAuth2 filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-oauth2-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ OAuth2: OAuth2 # required
+ authorizationURL: string # required
+
+ #------------------------------------------------------------------#
+ # OAuth Client settings #
+ #------------------------------------------------------------------#
+ expirationSafetyMargin: Duration # optional
+ grantType: Enum # optional, default="AuthorizationCode"
+ clientAuthentication: # optional
+ method: "enum" # optional, default="HeaderPassword"
+ jwtAssertion: # optional if `method: "JWTAssertion"`, forbidden otherwise
+ setClientID: bool # optional, default=false
+ audience: string # optional, default is to use the token endpoint from the authorization URL
+ signingMethod: Enum # optional, default="RS256"
+ lifetime: Duration # optional, default="1m"
+ setNBF: bool # optional, default=false
+ nbfSafetyMargin: Duration # optional, default=0s
+ setIAT: bool # optional, default=false
+ otherClaims: # optional, default={}
+ "string": anything
+ otherHeaderParameters: # optional; default={}
+ "string": anything
+
+ ## OAuth Client settings when `grantType: "AuthorizationCode"`
+ clientURL: string # deprecated, use 'protectedOrigins' instead
+ protectedOrigins: []ProtectedOrigin # required, minItems: 1
+ - origin: string # required
+ internalOrigin: string # optional, default is to just use the 'origin' field
+ includeSubdomains: bool # optional, default=false
+ useSessionCookies: # optional, default={ value: false }
+ value: bool # optional, default=true
+ ifRequestHeader: # optional, default to apply "useSessionCookies.value" to all requests
+ name: string # required
+ negate: bool # optional, default=false
+ value: string # optional, default is any non-empty string
+ valueRegex: string # optional, default is any non-empty string
+ clientSessionMaxIdle: Duration # optional, default is to use the access token lifetime or 14 days if a refresh token is present
+ postLogoutRedirectURI: string # optional
+ extraAuthorizationParameters: map[string]string # optional, default={}
+ "string": "string"
+
+ ## OAuth Client settings when `grantType: "AuthorizationCode"/"Password"`
+ clientID: string # required
+ secret: string # required (unless secretName is set)
+ secretName: string # required (unless secret is set)
+ secretNamespace: string # optional, default is the same namespace as the Filter
+
+ #------------------------------------------------------------------#
+ # OAuth Resource Server settings #
+ #------------------------------------------------------------------#
+ allowMalformedAccessToken: bool # optional, default=false
+ accessTokenValidation: Enum # optional, default="auto"
+ accessTokenJWTFilter: # optional
+ name: string # required
+ namespace: string # optional, default is the same namespace as the Filter
+ inheritScopeArgument: bool # optional, default=false
+ stripInheritedScope: bool # optional, default=false
+ arguments: JWTFilterArguments # optional
+ injectRequestHeaders: # optional
+ - name: string # required
+ value: string # required
+
+ #------------------------------------------------------------------#
+ # HTTP client settings for talking with the identity provider #
+ #------------------------------------------------------------------#
+ insecureTLS: bool # optional, default=false
+ renegotiateTLS: Enum # optional, default="never"
+ maxStale: Duration # optional, default="0"
+```
+
+### OAuth2Filter
+
+| **Field** | **Type** | **Description** |
+|-------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `authorizationURL` | `string` | Identity Provider Issuer URL which hosts the OpenID provider well-known configurartion. The URL must be an absolute URL. Per [OpenID Connect Discovery 1.0][] the configuration must be provided in a json document at the path `/.well-known/openid-configuration`. This is used by the OAuth2 Filter for determining things like the AuthorizationEndpoint, TokenEndpoint, JWKs endpint, etc... |
+| `expirationSafetyMargin` | [Duration][] | Sets a buffer to check if the Token is expired or is going to expire within the safety margin. This is to ensure the application has enough time to reauthenticate to adjust for clock skew and network latency. By default, no safety margin is added. If a token is received with an expiration less than this field, then the token is considered to already be expired. |
+| `grantType` | `Enum`(`"AuthorizationCode"`,`"ClientCredentials"`,`"Password"`,`"ResourceOwner"`) | Sets the Authorization Flow that the filter will use to authenticate the incoming request. |
+| `clientAuthentication` | [ClientAuthentication][] | Defines how the OAuth2 Filter will authenticate with the iDP token endpoint. By default, it will pass it along as password in the Authentication header. Depending on how your iDP is configured it might require a JWTAssertion or passing the password. |
+| `protectedOrigins` | \[\][ProtectedOrigin][] | (You determine these, and must register them with your identity provider) Identifies hostnames that can appropriately set cookies for the application. Only the scheme (`https://`) and authority (`example.com:1234`) parts are used; the path part of the URL is ignored |
+| `useSessionCookies` | [SessionCookies][] | By default, any cookies set by $productName$ will be set to expire when the session expires naturally. `useSessionCookies` may be used to cause session cookies to be used instead |
+| `clientSessionMaxIdle` | [Duration][] | Controls how long the session held by $productName$'s OAuth client will last until we automatically expire it. $productName$ creates a new session when submitting requests to the upstream backend server and sets a cookie containing the sessionID. When a user makes a request to a backend service protected by the OAuth2 Filter, the OAuth Client in Ambassador Edge Stack will use the sessionID contained in the cookie to fetch the access token (and optional refresh token) for the current session so that it can be used when submitting a request to the upstream backend service. This session has a limited lifetime before it expires or extended, prompting the user to log back in. Setting a `clientSessionMaxIdle` duration is useful when your IdP is configured to return a refresh token along with an access token from your IdP's authorization server. `clientSessionMaxIdle` can be set to match Ambassador Edge Stack OAuth client's session lifetime to the lifetime of the refresh token configured within the IdP. If this is not set, then we tie the OAuth client's session lifetime to the lifetime of the access token received from the IdP's authorization server when no refresh token is also provided. If there is a refresh token, then by default we set it to be 14 days |
+| `postLogoutRedirectURI` | `string` | Set this field to a valid URL to have $productName$ redirect there upon a successful logout. You must register the following endpoint with your IDP as the Post Logout Redirect `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect`. This informs your IDP to redirect back to $productName$ once the IDP has cleared the session data. Once the IDP has redirected back to $productName$, this clears the local $productName$ session information before redirecting to the destination specified by the `postLogoutRedirectURI` value. If Post Logout Redirect is configured in your IDP to `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` then, after a successful logout, a redirect is issued to the URL configured in `postLogoutRedirectURI`. If `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` is configured as the Post Logout Redirect in your IDP, but `postLogoutRedirectURI` is not configured in $productName$, then your IDP will error out as it will be expecting specific instructions for the post logout behavior. Refer to your IDP’s documentation to verify if it supports Post Logout Redirects. For more information on `post_logout_redirect_uri functionality`, refer to the [OpenID Connect RP-Initiated Logout 1.0 specs](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) |
+| `extraAuthorizationParameters`| `map`\[`string`\]`string` | Extra (non-standard or extension) OAuth authorization parameters to use. It is not valid to specify a parameter used by OAuth itself ("response_type", "client_id", "redirect_uri", "scope", or "state") |
+| `clientID` | `string` | The Client ID you get from your identity provider |
+| `secret` | `string` | The client secret you get from your identity provider as a string. It is invalid to configure both `secret` and `secretName` |
+| `secretName` | `string` | The client secret you get from your identity provider as a Kubernetes `generic` Secret, named by `secretName`/`secretNamespace`. The Kubernetes secret must of the `generic` type, with the value stored under the key`oauth2-client-secret`. If `secretNamespace` is not given, it defaults to the namespace of the Filter resource. It is invalid to configure both `secret` and `secretName` |
+| `secretNamespace` | `string` | The client secret you get from your identity provider as a Kubernetes `generic` Secret, named by `secretName`/`secretNamespace`. The Kubernetes secret must of the `generic` type, with the value stored under the key`oauth2-client-secret`. If `secretNamespace` is not given, it defaults to the namespace of the Filter resource. It is invalid to configure both `secret` and `secretName` |
+| `allowMalformedAccessToken` | `bool` | Allow any access token, even if they are not RFC 6750-compliant. |
+| `accessTokenValidation` | `Enum`(`"jwt"`,`"userinfo"`,`"auto"`) | How to verify the liveness and scope of Access Tokens issued by the identity provider. Empty or unset is equivalent to `"auto"` |
+| `accessTokenJWTFilter` | [AccessTokenJWTFilter][] | Used to identify a JWT Filter to use for validating access token JWTs. It is an error to point at a Filter that is not a JWT filter |
+| `injectRequestHeaders` | \[\][AddHeaderTemplate][] | injects HTTP header fields in to the request before sending it to the upstream service; where the header value can be set based on the JWT value. If an OAuth2 filter is chained with a JWT filter with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the OAuth2 filter will populate the value. The value is specified as a Go `text/template` string |
+| `insecureTLS` | `bool` | disables TLS verification when speaking to an identity provider with an `https://` `authorizationURL`. This is discouraged in favor of either using plain `http://` or [installing a self-signed certificate][] |
+| `renegotiateTLS` | `Enum`(`"never"`,`"onceAsClient"`,`"freelyAsClient"`) | Allows a remote server to request TLS renegotiation |
+| `maxStale` | [Duration][] | How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also **ignores the `no-store` and `no-cache` Cache-Control directives on responses**. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Setting to 0 means that it will default back to the identity provider's default cache settings as specified by the Cache-Control directives on responses which may include no caching depending if the identity provider sets the `no-cache` and `no-store` directives. Note that if you are reusing the same `authorizationURL` and `jwksURI` across different OAuth and JWT filters respectively, then you MUST set `maxStale` as a consistent value on each filter to get predictable caching behavior |
+
+**`grantType` options**:
+
+- `"AuthorizationCode"`: Authenticate by redirecting to a login page served by the identity provider.
+- `"Password"`: Authenticate by requiring `X-Ambassador-Username` and `X-Ambassador-Password` on all incoming requests, and use them to authenticate with the identity provider using the OAuth2 Resource Owner Password Credentials grant type.
+- `"ClientCredentials"`: Authenticate by requiring that the incoming HTTP request include as headers the credentials for Ambassador to use to authenticate to the identity provider.
+ - The type of credentials needing to be submitted depends on the `clientAuthentication.method` (below):
+ - For `"HeaderPassword"` and `"BodyPassword"`, the headers `X-Ambassador-Client-ID` and `X-Ambassador-Client-Secret` must be set.
+ - For `"JWTAssertion"`, the `X-Ambassador-Client-Assertion` header must be set to a JWT that is signed by your client secret, and conforms with the requirements in RFC 7521 section 5.2 and RFC 7523 section 3, as well as any additional specified by your identity provider.
+
+**`accessTokenValidation` options**:
+
+- `"jwt"`: Validates the Access Token as a JWT.
+
+ - By default: It accepts the RS256, RS384, or RS512 signature algorithms, and validates the signature against the JWKS from
+OIDC Discovery. It then validates the `exp`, `iat`, `nbf`, `iss` (with the Issuer from OIDC Discovery), and `scope` claims: if present,
+none of the scope values are required to be present. This relies on the identity provider using non-encrypted signed JWTs as
+Access Tokens, and configuring the signing appropriately
+ - This behavior can be modified by delegating to [JWT Filter][] with `accessTokenJWTFilter`:
+
+- `"userinfo"`: Validates the access token by polling the OIDC UserInfo Endpoint. This means that $productName$ must initiate
+an HTTP request to the identity provider for each authorized request to a protected resource. This performs poorly,
+but functions properly with a wider range of identity providers. It is not valid to set `accessTokenJWTFilter` if
+`accessTokenValidation`: `userinfo`.
+
+- `"auto"` attempts to do `"jwt"` validation if any of these conditions are true:
+ - `accessTokenJWTFilter` is set
+ - `grantType` is `"ClientCredentials"`
+ - the Access Token parses as a JWT and the signature is valid,
+ - If none of the above conditions are satisfied, it falls back to `"userinfo"` validation.
+
+### Duration
+
+Duration is a field that accepts a string that will be parsed as a sequence of decimal numbers ([metav1.Duration][]), each with optional fraction
+and a unit suffix, such as `"300ms"`, `"1.5h"` or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`,
+`"m"`, `"h"`. See [Go time.ParseDuration][].
+
+### ClientAuthentication
+
+Configures how Ambassador uses the `clientID` and `secret` to authenticate itself to the identity provider
+
+| **Field** | **Type** | **Description** |
+|----------------|----------------------------------|---------------------------------------------------------------------------------------------------------|
+| `method` | `Enum`(`"HeaderPassword"`,`"BodyPassword"`,`"JWTAssertion"`) | Defines the type of client authentication that will be used |
+| `jwtAssertion` | [JWTAssertion][] | This field is only used when `method: "JWTAssertion"`. Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow. |
+
+`method` options:
+
+- `"HeaderPassword"`: Treat the client secret as a password, and pack that in to an HTTP header for HTTP Basic authentication.
+- `"BodyPassword"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+- `"JWTAssertion"`: Treat the client secret as a password, and put that in the HTTP request bodies submitted to the identity provider. This is NOT RECOMMENDED by RFC 6749, and should only be used when using `HeaderPassword` isn't possible.
+
+### JWTAssertion
+
+Allows setting a [JWT Filter][] with custom settings on how to verify JWT obtained via the OAuth2 flow.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|-------------------------|---------------------------------------------------------------------------------------------------------|
+| `setClientID` | `bool` | Whether to set the Client ID as an HTTP parameter; setting it as an HTTP parameter is optional (per RFC 7521 §4.2) because the Client ID is also contained in the JWT itself, but some identity providers document that they require it to also be set as an HTTP parameter anyway. |
+| `audience` | `string` | This field is ignored when `grantType: "ClientCredentials"`. The audience your IDP requires for authentication. If not set then the default will be to use the token endpoint from the OIDC discovery document. |
+| `signingMethod` | [ValidAlgorithms][] | The set of signing algorithms that can be considered when verifying tokens attached to requests. If the token is signed with an algorithm that is not in this list then it will be rejected. If not provided then all supported algorithms are allowed. The list should match the set configured in the iDP, as well as the full set of possible valid tokens maybe received. For example, if you may have previously supported RS256 & RS512 but you have decided to only receive tokens signed using RS512 now. This will cause existing tokens to be rejected. |
+| `lifetime` | [Duration][] | This field is ignored when `grantType: "ClientCredentials"`. The lifetime of the generated JWT; just enough time for the request to the identity provider to complete (plus possibly an extra allowance for clock skew). |
+| `setNBF` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "nbf" ("Not Before") claim in the generated JWT. |
+| `nbfSafetyMargin` | [Duration][] | This field is only used when `setNBF: true` The safety margin to build-in to the "nbf" claim, to allow for clock skew between ambassador and the identity provider. |
+| `setIAT` | `bool` | This field is ignored when `grantType: "ClientCredentials"`. Whether to set the optional "iat" ("Issued At") claim in the generated JWT. |
+| `otherClaims` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Key/value pairs that will be add to the JWT sent for client Auth to the Identity Provider |
+| `otherHeaderParameters` | `[]byte` (Encoded JSON) | This field is ignored when `grantType: "ClientCredentials"`. Any extra JWT header parameters to include in the generated JWT non-standard claims to include in the generated JWT; only the "typ" and "alg" header parameters are set by default. |
+
+### ValidAlgorithms
+
+Valid Algorithms is an enum with quite a few entries, the possible values are:
+
+- `"none"`
+- **ECDSA Algorithms**: `"ES256"`, `"ES384"`, `"ES512"`
+ - The secret must be a PEM-encoded Eliptic Curve private key
+- **HMAC-SHA Algorithms**: `"HS256"`, `"HS384"`, `"HS512"`
+ - The secret is a raw string of bytes; it can contain anything
+- **RSA-PSS Algorithms**: `"PS256"`, `"PS384"`, `"PS512"`
+ - The secret must be a PEM-encoded RSA private key
+- **RSA Algorithms**: `"RS256"`, `"RS384"`, `"RS512"`
+ - The secret must be a PEM-encoded RSA private key
+
+### ProtectedOrigin
+
+You determine these, and must register them with your identity provider. Identifies hostnames that can
+appropriately set cookies for the application. Only the scheme (`https://`) and authority (`example.com:1234`) parts are used; the
+path part of the URL is ignored. You will need to register each origin in `protectedOrigins` as an authorized callback endpoint with your identity provider. The URL
+will look like `{{ORIGIN}}/.ambassador/oauth2/redirection-endpoint`.
+
+
+
+
+If you provide more than one `protectedOrigin`, all share the same
+authentication system, so that logging into one origin logs you
+into all origins; to have multiple domains that have separate
+logins, use separate `Filter`s.
+
+| **Field** | **Type** | **Description** |
+|--------------------------|------------|---------------------------------------------------------------------------------------------------------|
+| `origin` | `string` | The absolute URL (schema://hostname) that is protected by the OAuth2 Filter |
+| `includeSubdomains` | `bool` | Enables protecting sub-domains of the domain identified in the Origin field. Example, when `Origin=https://example.com` then the subdomain of `https://app.example.com` would be watched. |
+| `allowedInternalOrigins` | `[]string` | Indentifies a list of allowed internal origins that were set by a downstream proxy via a host header rewrite. The origins identified in this list ensures the request is allowed and will ensure it redirects correctly to the upstream origin. For example, a downstream client will communicate with an origin of `https://example.com` but then an internal proxy will do a rewrite so that the host header received by Edge Stack is `http://example.internal`. |
+
+**Note about `allowedInternalOrigins`**: This field is primarily used to allow you to tell $productName$ that there is another gateway
+in front of $productName$ that rewrites the Host header, so that on the internal network between that gateway and $productName$, the
+origin appears to be `allowedInternalOrigins` instead of `origin`. As a special-case the scheme and/or authority of the `allowedInternalOrigins`
+may be `"*"`, which matches any scheme or any domain respectively.
+Using `"*"` is most useful in configurations with exactly one protected origin; in such a configuration, $productName$ doesn't need
+to know what the origin looks like on the internal network, just that a gateway in front of $productName$ is rewriting it.
+It is invalid to use `"*"` with `includeSubdomains: true`.
+
+For example, if you have a gateway in front of $productName$ handling traffic for `myservice.example.com`, terminating TLS and routing
+that traffic to $productName$ with the name `example.internal`, you might write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - http://example.internal
+```
+
+or, to avoid being fragile to renaming example.internal to something else, since there are not multiple origins that the `Filter` must
+distinguish between, you could instead write:
+
+```yaml
+- origin: https://myservice.example.com
+ allowedInternalOrigins:
+ - "*://*"
+```
+
+### AddHeaderTemplate
+
+List of headers that will be injected into the upstream request if allowed through. The headers can pull information from the Token has values. For example, attaching user email claim to a header from the token.
+
+| **Field** | **Type** | **Description** |
+|------------|--------------------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | The name of the header to inject `value` into |
+| `value` | `string` (GoLang Template) | A Golang template that can dynamically extract request information as the value of the injected header. |
+
+The header value can be set based on the JWT value. If an `OAuth2 Filter` is chained with a [JWT filter][] with `injectRequestHeaders` configured, both sets of headers will be injected. If the same header is injected in both filters, the `OAuth2 Filter` will populate the value. The value is specified as a [Go text/template][] string, with the following data made available to it:
+
+- `.token.Raw` → The access token raw JWT (`string`)
+- `.token.Header` → The access token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.token.Claims` → The access token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.token.Signature` → The access token signature (`string`)
+- `.idToken.Raw` → The raw id token JWT (`string`)
+- `.idToken.Header` → The id token JWT header (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Claims` → The id token JWT claims (as parsed JSON: `map[string]interface{}`)
+- `.idToken.Signature` → The id token signature (`string`)
+- `.httpRequestHeader` → `http.Header` a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
+
+### SessionCookies
+
+By default, any cookies set by the $productName$ will be set to expire when the session expires naturally. The
+`useSessionCookies` setting may be used to cause session cookies to be used instead.
+
+
+
+- Normally cookies are set to be deleted at a specific time; session cookies are deleted whenever the user closes their web
+browser. This may mean that the cookies are deleted sooner than normal if the user closes their web browser; conversely, it may
+mean that cookies persist for longer than normal if the use does not close their browser.
+- The cookies being deleted sooner may or may not affect user-perceived behavior, depending on the behavior of the identity provider.
+- Any cookies persisting longer will not affect behavior of the system; Ambassador Edge Stack validates whether the session is expired when considering the cookie.
+
+If `useSessionCookies` is non-`null`, then:
+
+- By default it will have the cookies for all requests be session cookies or not according to the `useSessionCookies.value` sub-argument.
+- Setting the `useSessionCookies.ifRequestHeader` sub-argument tells it to use `useSessionCookies.value` for requests that match the condition, and `!useSessionCookies.value` for requests don't match.
+
+When determining if a request matches, it looks at the HTTP header field named by `useSessionCookies.ifRequestHeader.name` (case-insensitive), and checks if it is either set to (if `useSessionCookies.ifRequestHeader.negate: false`) or not set to (if `useSessionCookies.ifRequestHeader.negate: true`)...
+
+- a non-empty string (if neither `useSessionCookies.ifRequestHeader.value` nor `useSessionCookies.ifRequestHeader.valueRegex` are set)
+- the exact string `value` (case-sensitive) (if `useSessionCookies.ifRequestHeader.value` is set)
+- a string that matches the regular expression `useSessionCookies.ifRequestHeader.valueRegex` (if `valueRegex` is set). This uses [RE2][] syntax (always, not obeying `regex_type` in the `Module`) but does not support the `\C` escape sequence.
+- (it is invalid to have both `value` and `valueRegex` set)
+
+| **Field** | **Type** | **Description** |
+|-------------------|---------------------------|-----------------------------------------------------------------------------------|
+| `value` | `bool` |
+| `ifRequestHeader` | [HTTPHeaderMatch][] |
+
+### HTTPHeaderMatch
+
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of the HTTP Header to be matched. Only one of `value` or `valueRegex` can be configured |
+| `valueRegex` | `string` | Regex expression for matching the value of the HTTP Header. Only one of `value` or `valueRegex` can be configured. This uses [RE2][] syntax (always, not obeying `regex_type` in the `ambassador Module`) but does not support the `\C` escape sequence. |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### AccessTokenJWTFilter
+
+**Appears On**: [OAuth2Filter][]
+Reference to a [JWT Filter][] to be executed after the OAuth2 Filter finishes
+
+| **Field** | **Type** | **Description** |
+|------------------------|-------------------|---------------------------------------------------------------------------------------------------------|
+| `name` | `string` | Name of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `namespace` | `string` | Namespace of the JWTFilter used to verify AccessToken. Note the Filter refrenced here must be a JWTFilter. |
+| `inheritScopeArgument`:| `bool` | Will use the same scope as set on the FilterPolicy OAuth2Arguments. If the JWTFilter sets a scope as well then the union of the two will be used. |
+| `stripInheritedScope` | `bool` | Determines whether or not to santized a scope that is formatted as an URI and was inherited from the FilterPolicy OAuth2Arguments. This will be done prior to passing it along to the referenced JWTFilter. This requires that InheritScopeArgument is true. |
+| `arguments` | [JWTArguments][] | Defines the input arguments that can be set for a JWTFilter. |
+
+### JWTArguments
+
+Defines the input arguments that can be set for a JWTFilter.
+
+| **Field** | **Type** | **Description** |
+|------------|-------------|---------------------------------------------------------------------------------------------------------|
+| `scope` | `[]string` | A list of OAuth scope values to include in the scope of the authorization request. If one of the scope values for a path is not granted, then access to that resource is forbidden; if the `scope` argument lists `foo`, but the authorization response from the provider does not include `foo` in the scope, then it will be taken to mean that the authorization server forbade access to this path, as the authenticated user does not have the `foo` resource scope. |
+
+**Some notes about `scope`**:
+
+- If `grantType: "AuthorizationCode"`, then the `openid` scope value is always included in the requested scope, even if it is not listed.
+- If `grantType: "ClientCredentials"` or `grantType: "Password"`, then the default scope is empty. If your identity provider does not have a default scope, then you will need to configure one here.
+- As a special case, if the `offline_access` scope value is requested, but not included in the response then access is not forbidden. With many identity providers, requesting the `offline_access` scope is necessary to receive a Refresh Token.
+- The ordering of scope values does not matter, and is ignored.
+
+[AddHeaderTemplate]: #addheadertemplate
+[Oauth2Filter]: #oauth2filter
+[AccessTokenJWTFilter]: #accesstokenjwtfilter
+[ClientAuthentication]: #clientauthentication
+[JWTArguments]: #jwtarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[JWTAssertion]: #jwtassertion
+[ValidAlgorithms]: #validalgorithms
+[ProtectedOrigin]: #protectedorigin
+[SessionCookies]: #sessioncookies
+[Duration]: #duration
+[installing a self-signed certificate]: ../../../../topics/using/filters/#filters-using-self-signed-certificates
+[JWT Filter]: ../filter-jwt
+[the v1alpha1 OAuth2 Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-oauth2
+[Go time.ParseDuration]: https://pkg.go.dev/time#ParseDuration
+[OpenID Connect Discovery 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+[metav1.Duration]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
+[RE2]: https://github.com/google/re2/wiki/Syntax
diff --git a/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-plugin.md b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-plugin.md
new file mode 100644
index 000000000..264ed57b2
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter-plugin.md
@@ -0,0 +1,42 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **Plugin Filter** Type (v3alpha1)
+
+The Plugin filter type allows you to plug in your own custom code. This code is compiled into a .so file,
+which you load into the Envoy Proxy container at `/etc/ambassador-plugins/${NAME}.so`. For more information about how requests are
+matched to `Filter` resources and the order in which `Filters` are executed, please refer to the [FilterPolicy Resource][] documentation.
+
+
+
+This doc is an overview of all the fields on the `Plugin Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `Plugin Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 Plugin Filter api reference][].
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## Plugin Filter API Reference
+
+To create a Plugin Filter, the `spec.type` must be set to `plugin`, and the `plugin` field must contain the configuration for your Plugin Filter.
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-plugin-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ plugin: PluginFilter # required
+ name: string # required
+```
+
+`name`: Indicates the compiled binaries name excluding the extension for the Plugin.
+Envoy Proxy will look for the .so file in the `/etc/ambassador-plugins` directory.
+For example, if `name: "example-plugin"` the .so file should be available at
+`"/etc/ambassador-plugins/example-plugin.so"` on the Envoy Proxy container.
+
+[FilterPolicy Resource]: ../filterpolicy
+[the v1alpha1 Plugin Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter-plugin
diff --git a/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter.md b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter.md
new file mode 100644
index 000000000..fb65cb023
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filter.md
@@ -0,0 +1,64 @@
+
+import Alert from '@material-ui/lab/Alert';
+
+# The **Filter** Resource (v3alpha1)
+
+The `Filter` custom resource works in conjunction with the [FilterPolicy custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending them to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests, such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute against those requests. Filters are largely used to add built-in authentication and security, but
+$productName$ also supports developing custom filters to add your own processing and logic.
+
+This doc is an overview of all the fields on the `Filter` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `Filter` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 Filter api reference][].
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+
+ v3alpha1Filters can only be referenced from v3alpha1FilterPolicies.
+
+
+## v3alpha1 Filter API Reference
+
+Filtering is configured using `Filter` custom resources. The body of the resource `spec` depends on the filter type:
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: Filter
+metadata:
+ name: "example-filter"
+ namespace: "example-namespace"
+spec:
+ ambassador_id: []string # optional
+ JWT: JWTFilter # optional
+ OAuth2: OAuth2Filter # optional
+ APIKey: APIKeyFilter # optional
+ External: ExternalFilter # optional
+ plugin: PluginFilter # optional
+```
+
+### FilterSpec
+
+Other than `ambassador_id`, only one of the following fields may be configured. For example you cannot create a `Filter` with both
+`JWT` and `External`.
+
+| **Field** | **Type** | **Description** |
+|------------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------|
+| `ambassador_id` | \[\]`string` | Ambassador id accepts a list of strings that allow you to restrict which instances of $productName$ can use/view this resource. If `ambassador_id` is configured, then only Deployments of $productName$ with a matching `AMBASSADOR_ID` environment variable will be able to use this resource. |
+| `JWT` | [JWTFilter][] | Provides configuration for the JWT Filter type |
+| `OAuth2` | [OAuth2Filter][] | Provides configuration for the OAuth2 Filter type |
+| `APIKey` | [APIKeyFilter][] | Provides configuration for the APIKey Filter type |
+| `External` | [ExternalFilter][] | Provides configuration for the External Filter type |
+| `Plugin` | [PluginFilter][] | Provides configuration for the Plugin Filter type |
+
+[FilterPolicy custom resource]: ../filterpolicy
+[JWTFilter]: ../filter-jwt
+[PluginFilter]: ../filter-plugin
+[OAuth2Filter]: ../filter-oauth2
+[APIKeyFilter]: ../filter-apikey
+[ExternalFilter]: ../filter-external
+[the v1alpha1 Filter api reference]: ../../../gateway-getambassador/v1alpha1/filter
diff --git a/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filterpolicy.md b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filterpolicy.md
new file mode 100644
index 000000000..af34f45ab
--- /dev/null
+++ b/docs/edge-stack/pre-release/custom-resources/getambassador/v3alpha1/filterpolicy.md
@@ -0,0 +1,139 @@
+import Alert from '@material-ui/lab/Alert';
+
+# The **FilterPolicy** Resource (v3alpha1)
+
+The `FilterPolicy` custom resource works in conjunction with the [Filter custom resource][] to define how and when $productName$ will
+modify or intercept incoming requests before sending to your upstream Service. `Filters` define what actions to take on a request,
+while `FilterPolicies` define the matching criteria for requests such as the headers, hostname, and path, and supply references to
+one or more `Filters` to execute on those requests.
+
+
+
+This doc is an overview of all the fields on the `FilterPolicy` Custom Resource with descriptions of the purpose, type, and default values of those fields.
+This page is specific to the `getambassador.io/v3alpha1` version of the `FilterPolicy` resource. For the newer `gateway.getambassador.io/v1alpha1` resource,
+please see [the v1alpha1 FilterPolicy api reference][].
+
+
+ v3alpha1FilterPolicies can only be reference v3alpha1Filters.
+
+
+
+ Filtering actions of all types in $productName$ are only ever executed on incoming requests and not on responses from your upstream Services.
+
+
+## FilterPolicy API Reference
+
+```yaml
+---
+apiVersion: getambassador.io/v3alpha1
+kind: FilterPolicy
+metadata:
+ name: "example-filter-policy"
+ namespace: "example-namespace"
+spec: FilterPolicy
+ ambassador_id: []string # optional
+ rules: []FilterPolicyRule # required, minItems: 1
+ - host: string # required
+ path: string # required
+ precedence: int # optional
+ filters: []FilterReference # required, minItems: 1
+ - name: string # required
+ namespace: string # optional, default is the same namespace as the FilterPolicy
+ onDeny: Enum # optional, default="break"
+ onAllow: Enum # optional, default="continue"
+ ifRequestHeader: HTTPHeaderMatch # optional
+ name: string # required
+ value: string # optional, default is any non-empty string
+ valueRegex: string # optional, default is any non-empty string
+ negate: bool # optional, default=false
+ arguments: FilterArguments # optional
+```
+
+### FilterPolicy
+
+| **Field** | **Type** | **Description** |
+|------------------|----------------------------|-----------------------------------------------------------------------------------|
+| `ambassador_id` | \[\]`string` | Ambassador id accepts a list of strings that allow you to restrict which instances of $productName$ can use/view this resource. If `ambassador_id` is configured, then only Deployments of $productName$ with a matching `AMBASSADOR_ID` environment variable will be able to use this resource. |
+| `rules` | \[\][FilterPolicyRule][] | Set of matching rules that are checked against incoming request to determine which set of Filter's to apply. If no matches are found then the request is allowed through to the upstream service without executing any Filters. |
+
+### FilterPolicyRule
+
+Configures matching rules that are checked against incoming request to determine which `Filter` to apply (if any).
+
+| **Field** | **Type** | **Description** |
+|--------------|--------------------------|-----------------------------------------------------------------------------------|
+| `host` | `string` | "glob-string" that matches on the `:authority` header of the incoming request. If not set it will match on all incoming requests. |
+| `path` | `string` | "glob-string" that matches on the request path. If not provided then it will match on all incoming requests. |
+| `precedence` | `int` | Allows forcing a precedence ordering on the rules. By default the rules are evaluated in the order they are in the `FilterPolicy.spec.rules` field. However, multiple FilterPolicy's can be applied to a cluster. To ensure that a specific ordering is enforced then using a precedence is an option. |
+| `filters` | \[\][FilterReference][] | List of references to `Filters` that will be applied to the incoming request. Filters will be applied to the request in the order they are listed. If no filters are provided then the request will be allowed through to the upstream service without any additional processing. This allows for having one Rule that is overly permissive and then using a single rule to opt-out on certain paths. |
+
+**Note:** The wildcard `*` is supported for both `path` and `host`.
+
+When multiple Filters are specified in a rule:
+
+- The filters are gone through in order
+- Each filter may either:
+ - return a direct HTTP *response*, intended to be sent back to the requesting HTTP client (normally *denying* the request from being forwarded to the upstream service) OR
+ - return a modification to make to the HTTP *request* before sending it to other filters or the upstream service (normally *allowing* the request to be forwarded to the upstream service with modifications).
+- If a filter has an `ifRequestHeader` setting, the filter is skipped
+ unless the request (including any modifications made by earlier
+ filters) has the HTTP header field `name`
+ set to (or not set to if `negate: true`):
+ - a non-empty string if neither `value` nor `valueRegex` are set
+ - the exact string `value` (case-sensitive) (if `value` is set)
+ - a string that matches the regular expression `valueRegex` (if
+ `valueRegex` is set). This uses [RE2][] syntax (always, not
+ obeying [`regex_type`][] in the Ambassador module) but does not
+ support the `\C` escape sequence.
+- Modifications to the request are cumulative; later filters have access to _all_ headers inserted by earlier filters.
+
+### FilterReference
+
+A refernce to a filter to be executed when an incoming request matches the `FilterPolicy` Rule
+
+| **Field** | **Type** | **Description** |
+|-------------------|-----------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name that identifies the Filter |
+| `namespace` | `string` | Kubernetes namespace that the Filter resides. It must be a RFC 1123 label. Valid values include: `"example"`, Invalid values include: `"example.com"` (`.` is an invalid character). This validation is based off of the [corresponding Kubernetes validation]. |
+| `onDeny` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter denies the request. |
+| `onAllow` | `Enum` (`"break"`,`"continue"`) | Determines the behavior when a Filter allows the request. |
+| `ifRequestHeader` | [HTTPHeaderMatch][] | Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not. |
+| `arguments` | [FilterArguments][] | Untyped map that allows for additional configuration specific to each filter to be provided |
+
+**onDeny Options**:
+
+- `"break"`: End processing, and return the response directly to
+ the requesting HTTP client. Later filters are not called. The request is not forwarded to the upstream service.
+- `"continue"`: Continue processing. The request is passed to the
+ next filter listed; or if at the end of the list, it is forwarded to the upstream service. The HTTP response returned from the filter is discarded.
+
+**onAllow Options**:
+
+- `"break"`: Apply the modification to the request, then end filter processing, and forward the modified request to the upstream service. Later filters are not called.
+- `"continue"`: Continue processing. Apply the request modification, then pass the modified request to the next filter
+ listed; or if at the end of the list, forward it to the upstream service.
+
+### HTTPHeaderMatch
+
+Checks if exact or regular expression matches a value in a request Header to determine if an individual Filter is executed or not.
+
+| **Field** | **Type** | **Description** |
+|--------------|-----------------------------------------|-----------------------------------------------------------------------------------|
+| `name` | `string` | Name of the header to match. Matching MUST be case insensitive. (See [https://tools.ietf.org/html/rfc7230][]). Valid examples: `"Authorization"`/`"Set-Cookie"`. Invalid examples: `":method"` - `:` is an invalid character. This means that HTTP/2 pseudo headers are not currently supported by this type. `"/invalid"` - `/` is an invalid character. |
+| `value` | `string` | Value of the HTTP Header to be matched. Only one of `value` or `valueRegex` can be configured |
+| `valueRegex` | `string` | Regex expression for matching the value of the HTTP Header. Only one of `value` or `valueRegex` can be configured |
+| `negate` | `bool` | Allows the match criteria to be negated or flipped. For example, you can have a regex that checks for any non-empty string which would indicate would translate to if header exists on request then match on it. With negate turned on this would translate to match on any request that doesn't have a header. |
+
+### FilterArguments
+
+The Filter arguments fiels is an untyped map that allows for additional configuration specific to each filter to be provided.
+Refer to the usage guides for each filter type to see if it has any arguments that can be supplied.
+
+[FilterPolicyRule]: #filterpolicyrule
+[FilterReference]: #filterreference
+[FilterArguments]: #filterarguments
+[HTTPHeaderMatch]: #httpheadermatch
+[Filter custom resource]: ../filter
+[the v1alpha1 FilterPolicy api reference]: ../../../gateway-getambassador/v1alpha1/filterpolicy
+[corresponding Kubernetes validation]: https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go
+[https://tools.ietf.org/html/rfc7230]: https://tools.ietf.org/html/rfc7230
diff --git a/docs/edge-stack/pre-release/doc-links.yml b/docs/edge-stack/pre-release/doc-links.yml
index 50ebb708a..6d32f7e88 100644
--- a/docs/edge-stack/pre-release/doc-links.yml
+++ b/docs/edge-stack/pre-release/doc-links.yml
@@ -145,7 +145,7 @@
link: /howtos/linkerd2
- title: Technical reference
items:
- - title: Custom resources
+ - title: Using Custom Resources
items:
- title: The Host resource
link: /topics/running/host-crd
@@ -177,17 +177,17 @@
link: /howtos/client-cert-validation
- title: Filters
items:
- - title: Filters and Filter policies
+ - title: Using Filters and FilterPolicies
link: /topics/using/filters/
- - title: OAuth2 Filter
+ - title: Using OAuth2 Filters
link: /topics/using/filters/oauth2
- - title: JWT Filter
+ - title: Using JWT Filters
link: /topics/using/filters/jwt
- - title: External Filter
+ - title: Using External Filters
link: /topics/using/filters/external
- - title: Plugin Filter
+ - title: Using Plugin Filters
link: /topics/using/filters/plugin
- - title: API Keys Filter
+ - title: Using API Keys Filter
link: /topics/using/filters/apikeys
- title: Ingress and load balancing
items:
@@ -265,6 +265,46 @@
link: /topics/using/gateway-api
- title: Developer Portal
link: /topics/using/dev-portal
+ - title: CRD API References
+ items:
+ - title: getambassador.io/v3alpha1
+ items:
+ - title: Filter
+ link: /custom-resources/getambassador/v3alpha1/filter
+ items:
+ - title: The OAuth2 Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-oauth2
+ - title: The JWT Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-jwt
+ - title: The External Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-external
+ - title: The APIKey Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-apikey
+ - title: The Plugin Filter type
+ link: /custom-resources/getambassador/v3alpha1/filter-plugin
+ - title: FilterPolicy
+ link: /custom-resources/getambassador/v3alpha1/filterpolicy
+ - title: gateway.getambassador.io/v1alpha1
+ items:
+ - title: Filter
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter
+ items:
+ - title: The OAuth2 Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-oauth2
+ - title: The JWT Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-jwt
+ - title: The External Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-external
+ - title: The APIKey Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-apikey
+ - title: The Plugin Filter type
+ link: /custom-resources/gateway-getambassador/v1alpha1/filter-plugin
+ - title: FilterPolicy
+ link: /custom-resources/gateway-getambassador/v1alpha1/filterpolicy
+ - title: WebApplicationFirewall
+ link: /custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall
+ - title: WebApplicationFirewallPolicy
+ link: /custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy
- title: FAQs
link: /about/faq
- title: Troubleshooting
diff --git a/docs/edge-stack/pre-release/howtos/advanced-rate-limiting.md b/docs/edge-stack/pre-release/howtos/advanced-rate-limiting.md
index 1c6f6ff51..491b71cd0 100644
--- a/docs/edge-stack/pre-release/howtos/advanced-rate-limiting.md
+++ b/docs/edge-stack/pre-release/howtos/advanced-rate-limiting.md
@@ -1,6 +1,6 @@
# Advanced rate limiting
-$productName$ features a built-in [Rate Limit Service (RLS)](../../topics/running/services/rate-limit-service/#external-rate-limit-service). The $productName$ RLS uses a decentralized configuration model that enables individual teams the ability to independently manage [rate limits](https://www.getambassador.io/learn/kubernetes-glossary/rate-limiting) independently.
+$productName$ features a built-in [Rate Limit Service (RLS)](../../topics/running/services/rate-limit-service/#external-rate-limit-service). The $productName$ RLS uses a decentralized configuration model that enables individual teams the ability to independently manage [rate limits](https://www.getambassador.io/kubernetes-glossary/rate-limiting) independently.
All of the examples on this page use the backend service of the quote sample application to illustrate how to perform the rate limiting functions.
diff --git a/docs/edge-stack/pre-release/howtos/token-ratelimit.md b/docs/edge-stack/pre-release/howtos/token-ratelimit.md
index 42a6b454e..35912ac92 100644
--- a/docs/edge-stack/pre-release/howtos/token-ratelimit.md
+++ b/docs/edge-stack/pre-release/howtos/token-ratelimit.md
@@ -57,7 +57,7 @@ spec:
## 1. Configure the Filter to extract the claim
-In order to extract the claim, we need to have the Filter use the `injectRequestHeader` config and use a golang template to pull out the exact value of the `name` claim in our access token JWT and put it in a Header for our RateLimit to catch. Configuration is similar for both [OAuth2](../../topics/using/filters/oauth2/#oauth-resource-server-settings) and [JWT](../../topics/using/filters/jwt/).
+In order to extract the claim, we need to have the Filter use the `injectRequestHeader` config and use a golang template to pull out the exact value of the `name` claim in our access token JWT and put it in a Header for our RateLimit to catch. Configuration is similar for both [OAuth2](../../topics/using/filters/oauth2/) and [JWT](../../topics/using/filters/jwt/).
```yaml
apiVersion: getambassador.io/v3alpha1
diff --git a/docs/edge-stack/pre-release/howtos/web-application-firewalls.md b/docs/edge-stack/pre-release/howtos/web-application-firewalls.md
index 2872d02f0..d2c6eb06f 100644
--- a/docs/edge-stack/pre-release/howtos/web-application-firewalls.md
+++ b/docs/edge-stack/pre-release/howtos/web-application-firewalls.md
@@ -3,9 +3,11 @@
description: Quickly block common attacks in the OWASP Top 10 vulnerabilities like cross-site-scripting (XSS) and SQL injection with Edge Stack's self-service Web Application Firewalls (WAF)
---
-# Web Application Firewalls in $productName$
+# Using Web Application Firewalls in $productName$
-[$productName$][] comes fully equipped with a Web Application Firewall solution (commonly referred to as WAF) that is easy to set up and can be configured to help protect your web applications by preventing and mitigating many common attacks. To accomplish this, the [Coraza Web Application Firewall library][] is used to check incoming requests against a user-defined configuration file containing rules and settings for the firewall to determine whether to allow or deny incoming requests.
+[$productName$][] comes fully equipped with a Web Application Firewall solution (commonly referred to as WAF) that is easy to set up and can be configured to help protect your web applications by preventing and mitigating many common attacks. To accomplish this, the [Coraza Web Application Firewall library][] is used to check incoming requests against a user-defined configuration file containing rules and settings for the firewall to determine whether to allow or deny incoming requests.
+
+
$productName$ also has additional authentication features such as [Filters][] and [Rate Limiting][]. When `Filters`, `Ratelimits`, and `WebApplicationFirewalls` are all used at the same time, the order of operations is as follows and is not currently configurable.
@@ -13,122 +15,11 @@ $productName$ also has additional authentication features such as [Filters][] an
2. `Filters` are executed next (so long as any configured `WebApplicationFirewalls` did not already reject the request)
3. Lastly `Ratelimits` are executed (so long as any configured `WebApplicationFirewalls` and Filters did not already reject the request)
-## The Web Application Firewalls Resource
-
-In $productName$, the `WebApplicationFirewall` resource defines the configuration for an instance of the firewall.
-
-```yaml
----
-apiVersion: gateway.getambassador.io/v1alpha1
-kind: WebApplicationFirewall
-metadata:
- name: "example-waf"
- namespace: "example-namespace"
-spec:
- ambassadorSelector: # optional; default = {ambassadorIds: ["default"]}
- ambassadorIds: []string # optional; default = ["default"]
- firewallRules: # required; One of configMapRef;file;http must be set below
- sourceType: "enum" # required; allowed values are file;configmap;http
- configMapRef: # optional
- name: "string" # required
- namespace: "string" # required
- key: "string" # required
- file: "string" # optional
- http: # optional
- url: "string" # required; must be a valid URL.
- logging: # optional
- onInterrupt: # required
- enabled: bool # required
-status: # set and updated by application
- conditions: []metav1.Condition
-```
-
-- `ambassadorSelector` Configures how this resource is allowed to be watched/used by instances of Edge Stack
- - `ambassadorIds`: This optional field allows you to limit which instances of $productName$ can watch and use this resource. This allows for the separation of resources when running multiple instances of $productName$ in the same Kubernetes cluster. Additional documentation on [configuring Ambassador IDs can be found here][]. By default, all instances of $productName$ will be able to watch and use this resource.
-- `firewallRules`: Defines the rules to be used for the Web Application Firewall
- - `sourceType`: Identifies which method is being used to load the firewall rules. Value must be one of `configMapRef`;`file`;`http`. The value corresponds to the following fields for configuring the selected method.
- - `configMapRef`: Defines a reference to a `ConfigMap` in the Kubernetes cluster to load firewall rules from.
- - `name`: Name of the `ConfigMap`.
- - `namespace`: Namespace of the `ConfigMap`. It must be an RFC 1123 label. Valid values include: `"example"`. Invalid values include: `"example.com"` - `"."` is an invalid character. The maximum allowed length is `63` characters and the regex pattern `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` is used for validation.
- - `key`: The key in the `ConfigMap` to pull the rules data from.
- - `file`: Location of a file on disk to load the firewall rules from. Example: `"/ambassador/firewall/waf.conf`.
- - `http`: Configuration for downloading firewall rules from the internet. The rules will only be downloaded once when the `WebApplicationFirewall` is loaded. The rules will then be cached in-memory until a restart of $productName$ occurs or the `WebApplicationFirewall` is modified.
- - `url`: URL to fetch firewall rules from. If the rules are unable to be downloaded/parsed from the provided url for whatever reason, the requests matched to this `WebApplicationFirewall` will be allowed/denied based on the configuration of the `onError`
-- `logging`: Provides a way to configure additional logging in the $productName$ pods for the `WebApplicationFirewall`. This is in addition to the logging config that is available via the firewall configuration files. The following logs will always be output to the container logs when enabled.
- - `onInterrupt`: Controls logging behavior when the WebApplicationFirewall interrupts a request.
- - `enabled`: Configures whether the container should output logs.
-
-`status`: This field is automatically set to reflect the status of the `WebApplicationFirewall`.
-
-- `conditions`: Conditions describe the current conditions of the `WebApplicationFirewall`, known conditions are `Accepted`;`Ready`;`Rejected`.
-
-## The Web Application Firewalls Policy Resource
-
-The `WebApplicationFirewallPolicy` resource controls which requests to match on and which `WebApplicationFirewall` configuration to use. This gives users total control over the firewall configuration and when it is executed on requests. It is possible to set up multiple different firewall configurations for specific requests or a single firewall configuration that is applied to all requests.
-
-```yaml
----
-apiVersion: gateway.getambassador.io/v1alpha1
-kind: WebApplicationFirewallPolicy
-metadata:
- name: "example-waf-policy"
- namespace: "example-namespace"
-spec:
- ambassadorSelector: # optional; default = {ambassadorIds: ["default"]}
- ambassadorIds: []string # optional; default = ["default"]
- rules: # required
- - host: "string" # optional; default = * (runs on all hosts)
- path: "string" # optional; default = * (runs on all paths)
- ifRequestHeader: # optional
- type: "enum" # optional; allowed values are Exact;RegularExpression
- name: "string" # required
- value: "string" # optional
- negate: bool # optional; default: false
- wafRef: # required
- name: "string" # required
- namespace: "string" # required
- onError: # optional; min=400, max=599
- statusCode: int # required
- precedence: int # optional
-status: # set and updated by application
- conditions: []metav1.Condition
- ruleStatuses:
- - index: int
- host: "string"
- path: "string"
- conditions: []metav1.Condition
-```
-
-`spec`: Defines which requests to match on and which `WebApplicationFirewall` to be used against those requests.
-
-- `ambassadorSelector` Configures how this resource is allowed to be watched/used by instances of Edge Stack
- - `ambassadorIds`: This optional field allows you to limit which instances of $productName$ can watch and use this resource. This allows for the separation of resources when running multiple instances of $productName$ in the same Kubernetes cluster. Additional documentation on [configuring Ambassador IDs can be found here][]. By default, all instances of $productName$ will be able to watch and use this resource.
-- `rules`: This object configures matching requests and executes `WebApplicationFirewall`s on them.
- - `host`: Host is a "glob-string" that matches on the `:authority` header of the incoming request. If not set, it will match on all incoming requests.
- - `path`: Path is a "glob-string" that matches on the request path. If not provided, then it will match on all incoming requests.
- - `ifRequestHeader`: Checks if exact or regular expression matches a value in a request header to determine if the `WebApplicationFirewall` is executed or not.
- - `type`: Specifies how to match against the value of the header. Allowed values are `Exact`;`RegularExpression`
- - `name`: Name of the HTTP Header to be matched. Name matching MUST be case-insensitive. (See )
- - `value`: Value of HTTP Header to be matched. If type is `RegularExpression`, then this must be a valid regex with a length of at least 1.
- - `negate`: Allows the match criteria to be negated or flipped.
- - `wafRef`: A reference to a `WebApplicationFirewall` to be applied against the request.
- - `name`: Identifies the `WebApplicationFirewall`
- - `namespace`: Namespace of the `WebApplicationFirewall`. This field is required. It must be a RFC 1123 label. Valid values include: `"example"`. Invalid values include: `"example.com"` - `"."` is an invalid character. The maximum allowed length is `63` characters, and the regex pattern `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` is used for validation.
- - `onError`: provides a way to configure how requests are handled when a request matches the rule but there is a configuration or runtime error. By default, requests are allowed on error if this field is not configured. This covers runtime errors such as those caused by networking/request parsing as well as configuration errors such as if the `WebApplicationFirewall` that is referenced is misconfigured, cannot be found, or when its configuration cannot be loaded properly. Details about the errors can be found either in the `WebApplicationFirewall` status or container logs.
- - `statusCode`: The status code to return to downstream clients when an error occurs.
- - `precedence`: Allows forcing a precedence ordering on the rules. By default the rules are evaluated in the order they are in the `WebApplicationFirewallPolicy.spec.rules` field. However, multiple `WebApplicationFirewallPolicys` can be applied to a cluster. `precedence` can optionally be used to ensure that a specific ordering is enforced.
-
-`status`: This field is automatically set to reflect the status of the `WebApplicationFirewallPolicy`.
-
-- `conditions`: Conditions describe the current conditions of the `WebApplicationFirewallPolicy`, known conditions are `Accepted`;`Ready`;`Rejected`. If any rules have an error then the whole `WebApplicationFirewallPolicyPolicy` will be rejected.
-- `ruleStatuses`:
- - `index`: Provides the zero-based index in the list of Rules to help identify the rule with an error.
- - `host`: host of the rule with the error
- - `path`: path of the rule with the error
- - `conditions`: Describe the current condition of this Rule. Known values are `Accepted`;`Ready`;`Rejected`. If any rules have an error then the whole `WebApplicationFirewallPolicy` will be rejected.
-
## Quickstart
+See the [WebAplicationFirewall API reference][] and [WebAplicationFirewallPolicy API reference][]
+pages for an overview of all the supported fields of the following custom resources.
+
1. First, start by creating your firewall configuration. The example will download [the firewall rules][] published by [Ambassador Labs][], but you are free to write your own or use the published rules as a reference.
```yaml
@@ -242,7 +133,8 @@ To make using $productName$'s Web Application Firewall system easier and to enab
[Grafana]: https://grafana.com/
[Prometheus]: https://prometheus.io/docs/introduction/overview/
[Prometheus and Grafana documentation]:../prometheus
-[configuring Ambassador IDs can be found here]: ../../topics/running/running#ambassador_id
+[WebAplicationFirewall API reference]: ../../custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewall
+[WebAplicationFirewallPolicy API reference]: ../../custom-resources/gateway-getambassador/v1alpha1/webapplicationfirewallpolicy
[$productName$]: https://www.getambassador.io/products/edge-stack/api-gateway
[Ambassador Labs]: https://www.getambassador.io/
[Configuring $productName$'s Web Application Firewall rules]: ../web-application-firewalls-config
diff --git a/docs/edge-stack/pre-release/releaseNotes.yml b/docs/edge-stack/pre-release/releaseNotes.yml
index 87433bd7d..5fc4a845b 100644
--- a/docs/edge-stack/pre-release/releaseNotes.yml
+++ b/docs/edge-stack/pre-release/releaseNotes.yml
@@ -32,6 +32,83 @@
changelog: https://github.com/datawire/edge-stack/blob/$branch$/CHANGELOG.md
items:
+ - version: 3.9.0
+ date: '2023-11-13'
+ notes:
+ - title: gateway.getambassador.io/v1alpha1 Filter & FilterPolicy resources
+ type: feature
+ body: >-
+ Filter and FilterPolicy resources are now available via gateway.getambassador.io/v1alpha1. It is NOT backwards compatible with getambassador.io/v3alpha1 Filter and FilterPolicy resources. These are the next generation of CRD's that you can
+ progressively adopt over time. They provide stronger typings so that
+ feedback is given at apply time rather than runtime.
+ docs: custom-resources/gateway-getambassador/v1alpha1/filter
+
+ - title: getambassador.io/v3alpha1 Filter & FilterPolicy statuses
+ type: feature
+ body: >-
+ Filter and FilterPolicy resources for the getambassador.io/v3alpha1 version will now provide statuses when they are "Ready". If there are
+ configuration errors they will provide an error message with details about the configuration issues. This will help troubleshoot configuration issues.
+
+ docs: custom-resources/getambassador/v3alpha1/filter
+
+ - title: Upgrade to Envoy 1.27.2
+ type: feature
+ body: >-
+ This upgrades $productName$ to be built on Envoy v1.27.2 which provides security, performance and feature enhancements. You can read more about them here: Envoy Proxy 1.27.2 Release Notes
+ docs: https://www.envoyproxy.io/docs/envoy/v1.27.2/version_history/version_history
+
+ - title: Added support for RESOURCE_EXHAUSTED responses to grpc clients when rate limited
+ type: feature
+ body: >-
+ By default, $productName$ will return an UNAVAILABLE code when a request using gRPC is rate limited. The RateLimitService resource now exposes a new grpc.use_resource_exhausted_code field that when set to true, $productName$ will return a RESOURCE_EXHAUSTED gRPC code instead. Thanks to Jerome Froelich for contributing this feature!
+ docs: topics/using/running/aes-extensions/ratelimit
+
+ - title: Added support for setting specific Envoy runtime flags in the Module
+ type: feature
+ body: >-
+ Envoy runtime fields that were provided to mitigate the recent HTTP/2 rapid reset
+ vulnerability can now be configured via the Module resource so the configuration will
+ persist between restarts. This configuration is added to the Envoy bootstrap config, so
+ restarting Emissary is necessary after changing these fields for the configuration to take effect.
+ docs: topics/running/ambassador/#set-envoy-runtime-flags
+
+ - title: Update APIExt minimum TLS version
+ type: change
+ body: >-
+ APIExt would previously allow for TLS 1.0 connections. We have updated it to now only use a minimum TLS version of 1.3 to resolve security concerns.
+ docs: https://www.tenable.com/plugins/nessus/104743
+
+ - title: Shipped Helm chart v8.9.0
+ type: change
+ body: >-
+ - Update default image to $productName$ v3.9.0.
+ docs: https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$
+
+ - title: Ensure APIExt server is available before starting Edge Stack
+ type: bugfix
+ body: >-
+ The APIExt server provides CRD conversion between the stored version
+ v2 and the version watched for by $productName$ v3alpha1. Since this
+ component is required to operate $productName$, we have introduced
+ an init container that will ensure it is available before starting. This will help address some of the intermittent issues seen during
+ install and upgrades.
+ docs: https://artifacthub.io/packages/helm/datawire/edge-stack/$aesChartVersion$
+
+ - version: 3.8.2
+ date: '2023-10-11'
+ notes:
+ - title: Upgrade Envoy
+ type: security
+ body: >-
+ This release includes security patches to the current Envoy proxy version to address CVE 2023-44487 and includes a fix to determine if a client is making too many requests with premature resets. The connection is disconnected if more than 50 percent of resets are considered premature. Another fix is also included which exposes a runtime setting to control the limit on the number of HTTP requests processed from a single connection in a single I/O cycle to mitigate CPU starvation.
+ docs: topics/running/running/
+
+ - title: Upgrade Golang to 1.20.10
+ type: security
+ body: >-
+ Upgrading to the latest release of Golang as part of our general dependency upgrade process. This update resolves CVE-2023-39323 and CVE-2023-39325.
+ docs: https://go.dev/doc/devel/release#go1.20.minor
+
- version: 3.8.1
date: '2023-09-18'
notes:
@@ -40,7 +117,7 @@ items:
body: >-
Upgrading to the latest release of Golang as part of our general dependency upgrade process. This includes security fixes for CVE-2023-39318, CVE-2023-39319.
docs: https://go.dev/doc/devel/release#go1.20.minor
-
+
- version: 3.8.0
date: '2023-08-29'
notes:
@@ -1152,7 +1229,7 @@ items:
insteadOfRedirect) without a value or valueRegex
would erroneously behave as if valueRegex='^$', rather than performing a
simple presence check.
- docs: topics/using/filters/#filterpolicy-definition
+ docs: custom-resources/getambassador/v3alpha1/filterpolicy
- title: Fix support for for v2 Mappings with CORS
type: bugfix
diff --git a/docs/edge-stack/pre-release/topics/install/migration-matrix.md b/docs/edge-stack/pre-release/topics/install/migration-matrix.md
index 21253feaf..ead43c6e0 100644
--- a/docs/edge-stack/pre-release/topics/install/migration-matrix.md
+++ b/docs/edge-stack/pre-release/topics/install/migration-matrix.md
@@ -25,22 +25,24 @@ See the [instructions on updating $OSSproductName$](../../../../../emissary/$oss
| If you're running. | You can upgrade to |
|-----------------------------------------|----------------------------------------------------------------------------------|
+| $AESproductName$ 3.8.X | [$AESproductName$ $version$](../upgrade/helm/edge-stack-3.8/edge-stack-3.X) |
| $AESproductName$ 3.7.X | [$AESproductName$ $version$](../upgrade/helm/edge-stack-3.7/edge-stack-3.X) |
| $AESproductName$ $versionTwoX$ | [$AESproductName$ $version$](../upgrade/helm/edge-stack-2.5/edge-stack-3.X) |
| $AESproductName$ 2.4.X | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-2.4/edge-stack-2.X) |
| $AESproductName$ 2.0.X | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-2.0/edge-stack-2.X) |
| $AESproductName$ $versionOneX$ | [$AESproductName$ $versionTwoX$](../upgrade/helm/edge-stack-1.14/edge-stack-2.X) |
| $AESproductName$ prior to $versionOneX$ | [$AESproductName$ $versionOneX$](../../../../1.14/topics/install/upgrading) |
-| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/helm/emissary-3.8/edge-stack-3.X) |
+| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/helm/emissary-3.9/edge-stack-3.X) |
## If you installed $AESproductName$ manually by applying YAML
| If you're running. | You can upgrade to |
|-----------------------------------------|----------------------------------------------------------------------------------|
+| $AESproductName$ 3.8.X | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-3.8/edge-stack-3.X) |
| $AESproductName$ 3.7.X | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-3.7/edge-stack-3.X) |
| $AESproductName$ $versionTwoX$ | [$AESproductName$ $version$](../upgrade/yaml/edge-stack-2.5/edge-stack-3.X) |
| $AESproductName$ 2.4.X | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-2.4/edge-stack-2.X) |
| $AESproductName$ 2.0.X | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-2.0/edge-stack-2.X) |
| $AESproductName$ $versionOneX$ | [$AESproductName$ $versionTwoX$](../upgrade/yaml/edge-stack-1.14/edge-stack-2.X) |
| $AESproductName$ prior to $versionOneX$ | [$AESproductName$ $versionOneX$](../../../../1.14/topics/install/upgrading) |
-| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/yaml/emissary-3.8/edge-stack-3.X) |
+| $OSSproductName$ $ossVersion$ | [$AESproductName$ $version$](../upgrade/yaml/emissary-3.9/edge-stack-3.X) |
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md
index acbc1e4a8..e0d02c0ec 100644
--- a/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-2.5/edge-stack-3.X.md
@@ -90,7 +90,7 @@ You can refer to the [Major changes in $productName$ 3.x](../../../../../../abou
As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
-$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
## Migration Steps
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md
index ef874e5d2..c988b1123 100644
--- a/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.4/edge-stack-3.X.md
@@ -90,7 +90,7 @@ You can refer to the [Major changes in $productName$ 3.x](../../../../../../abou
As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
-$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
## Migration Steps
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md
index 9876747d8..ab9bbf890 100644
--- a/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.7/edge-stack-3.X.md
@@ -90,7 +90,7 @@ You can refer to the [Major changes in $productName$ 3.x](../../../../../../abou
As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
-$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
## Migration Steps
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md
new file mode 100644
index 000000000..141473272
--- /dev/null
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/helm/edge-stack-3.8/edge-stack-3.X.md
@@ -0,0 +1,152 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.8.X to $productName$ $version$ (Helm)
+
+
+ This guide covers migrating from $productName$ 3.8.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made using Helm.
+ If you did not originally install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+
+ Make sure that you have converted your External Filters to `protocol_version: "v3"` before upgrading.
+ If not set or set to `v2` then an error will be posted and a static response will be returned in $productName$ 3.Y.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+$productName$ 3 is functionally compatible with $productName$ 2.x, but with any major upgrade there are some changes to consider. Such as, Envoy removing support for V2 Transport Protocol features. Below we will outline some of these changes and things to consider when upgrading.
+
+### Resources to check before migrating to $version$.
+
+$productName$ 3.X has been upgraded from Envoy 1.17.X to Envoy 1.22 which removed support for the Envoy V2 Transport Protocol. This means all `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` must be updated to use the V3 Protocol. Additionally support for some of the runtime bootstrap flags has been removed.
+
+You can refer to the [Major changes in $productName$ 3.x](../../../../../../about/changes-3.y/) guide for an overview of the changes.
+
+1. $productName$ 3.2 fixed a bug with `Host.spec.selector\mappingSelector` and `Listener.spec.selector` not being properly enforced.
+ In previous versions, if only a single label from the selector was present on the resource then they would be associated. Additionally, when associating `Hosts` with `Mappings`, if the `Mapping` configured a `hostname` that matched the `hostname` of the `Host` then they would be associated regardless of the configuration of the `selector\mappingSelector` on the `Host`.
+
+ Before upgrading, review your Ambassador resources, and if you make use of the selectors, ensure that every other resource you want it to be associated with contains all the required labels.
+
+ The environment variable `DISABLE_STRICT_LABEL_SELECTORS` can be set to `"true"` on the $productName$ deployment to revert to the
+ old incorrect behavior to help prevent any configuration issues after upgrading in the event that not all manifests making use of the selectors have been corrected yet.
+
+ For more information on `DISABLE_STRICT_LABEL_SELECTORS` see the [Environment Variables page](../../../../../running/environment).
+
+
+2. Check Transport Protocol usage on all resources before migrating.
+
+ The `AuthService`, `RatelimitService`, `LogServices`, and External `Filters` that use the `grpc` protocol will now need to explicilty set `protocol_version: "v3"`. If not set or set to `v2` then an error will be posted and a static response will be returned.
+
+ `protocol_version` should be updated to `v3` for all of the above resources while still running $productName$ $versionTwoX$. As of version `2.3.z`+, support for `protocol_version` `v2` and `v3` is supported in order to allow migration from `protocol_version` `v2` to `v3` before upgrading to $productName$ $version$ where support for `v2` is removed.
+
+ Upgrading any application code for your own implementations of these services is very straightforward.
+
+ The following imports simply need to be updated to switch from Envoy's Transport Protocol `v2` to `v3`, and then the configuration for these resources can be updated to add the `protocl_version: "v3"` when the updated service is deployed.
+
+ `v2` Imports:
+ ```golang
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+ ```
+
+ `v3` Imports:
+ ```golang
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+ ```
+
+3. Check removed runtime changes
+
+ ```yaml
+ # No longer necessary because this was removed from Envoy
+ # $productName$ already was converted to use the compressor API
+ # https://www.envoyproxy.io/docs/envoy/v1.22.0/configuration/http/http_filters/compressor_filter#config-http-filters-compressor
+ "envoy.deprecated_features.allow_deprecated_gzip_http_filter": true,
+
+ # Upgraded to v3, all support for V2 Transport Protocol removed
+ "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
+ "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
+
+ # Developers will need to upgrade TracingService to V3 protocol which no longer supports HTTP_JSON_V1
+ "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
+
+ # V2 protocol removed so flag no longer necessary
+ "envoy.reloadable_features.enable_deprecated_v2_api": true,
+ ```
+
+4. Support for LightStep tracing driver removed
+
+
+ As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
+
+
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+
+## Migration Steps
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x to 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, use Helm to install $productName$ $version$. Start by
+ making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Then, update your $productName$ installation in the `$productNamespace$` namespace.
+ If necessary for your installation (e.g. if you were running with
+ `AMBASSADOR_SINGLE_NAMESPACE` set), you can choose a different namespace.
+
+ ```bash
+ helm upgrade -n $productNamespace$ \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n $productNamespace$ deployment/$productDeploymentName$ -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $productName$ 3.X.
+
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md
new file mode 100644
index 000000000..e6dbff12b
--- /dev/null
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/helm/emissary-3.9/edge-stack-3.X.md
@@ -0,0 +1,241 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $OSSproductName$ $version$ to $AESproductName$ $version$ (Helm)
+
+
+ This guide covers migrating from $OSSproductName$ $version$ to $AESproductName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation originally made using Helm.
+ If you did not install with Helm, see the YAML-based
+ upgrade instructions.
+
+
+You can upgrade from $OSSproductName$ to $AESproductName$ with a few simple commands. When you upgrade to $AESproductName$, you'll be able to access additional capabilities such as **automatic HTTPS/TLS termination, Swagger/OpenAPI support, API catalog, Single Sign-On, and more.** For more about the differences between $AESproductName$ and $OSSproductName$, see the [Editions page](/editions).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $OSSproductName$ $version$ and $AESproductName$
+$version$ side-by-side in the same cluster. This gives $AESproductName$ $version$
+and $AESproductName$ $version$ access to all the same configuration resources, with some
+important notes:
+
+1. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $AESproductName$ $version$ installation from
+ seeing a particular bit of $OSSproductName$ $version$ configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $AESproductName$ $version$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $AESproductName$ $version$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+2. **$AESproductName$ ACME and `Filter`s will be disabled while $OSSproductName$ is still running.**
+
+ Since $AESproductName$ and $OSSproductName$ share configuration, $AESproductName$ cannot
+ configure its ACME or other filter processors without also affecting $OSSproductName$. This
+ migration process is written to simply disable these $AESproductName$ features to make
+ it simpler to roll back, if needed. Alternate, you can isolate the two configurations
+ as described above.
+
+3. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The best way to avoid multiple agents when installing with Helm is to use
+ `--set emissary-ingress.agent.enabled=false` to tell Helm not to install a
+ new Agent with $productName$ $version$. Once testing is done, you can switch
+ Agents safely.
+
+4. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $OSSproductName$ 3.X that use selectors that will match
+ Pods from $AESproductName$ $version$, traffic will be erroneously split between
+ $OSSproductName$ 3.X and $AESproductName$ $version$. The labels used by $AESproductName$
+ $version$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $AESproductName$ $version$ in a separate cluster](../../../../migrate-to-3-alternate/).
+This permits absolute certainty that your $OSSproductName$ $version$ configuration will not be
+affected by changes meant for $AESproductName$ $version$, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is a six-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster; Helm will not do this for you. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $AESproductName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $OSSproductName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $AESproductName$ $version$.**
+
+
+ $productName$ requires a valid license or cloud connect token to start. You can refer to the quickstart guide
+ for instructions on how to obtain a free community license. Copy the cloud token command from the guide in Ambassador cloud for use below. If you already have a cloud connect token or
+ a valid enterprise license, then you can skip this step.
+
+
+ After installing the new CRDs, you need to install $AESproductName$ $version$ itself
+ **in the same namespace as your existing $OSSproductName$ $version$ installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+
+ Make sure that you set the various `create` flags when running Helm. This prevents
+ $AESproductName$ $version$ from trying to configure filters that will adversely affect
+ $OSSproductName$ $version$.
+
+
+ Start by making sure that your `datawire` Helm repo is set correctly:
+
+ ```bash
+ helm repo remove datawire
+ helm repo add datawire https://app.getambassador.io
+ helm repo update
+ ```
+
+ Typically, $OSSproductName$ $version$ was installed in the `emissary` namespace. If you installed
+ $OSSproductName$ $version$ in a different namespace, change the namespace in the commands below.
+
+ - If you do not need to set `AMBASSADOR_LABEL_SELECTOR`:
+
+ ```bash
+ helm install -n emissary \
+ --set emissary-ingress.agent.enabled=false \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n emissary deployment/edge-stack -w
+ ```
+
+ - If you do need to set `AMBASSADOR_LABEL_SELECTOR`, use `--set`, for example:
+
+ ```bash
+ helm install -n emissary \
+ --set emissary-ingress.agent.enabled=false \
+ --set emissary-ingress.env.AMBASSADOR_LABEL_SELECTOR="version-two=true" \
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n emissary deployment/edge-stack -w
+ ```
+
+
+ You must use the $productHelmName$ Helm chart to install $AESproductName$ $version$.
+
+
+3. **Test!**
+
+ Your $AESproductName$ $version$ installation should come up running with the configuration
+ resources used by $OSSproductName$ $version$, including `Listener`s and `Host`s.
+
+
+ If you find that your $AESproductName$ $version$ installation and your $OSSproductName$ $version$
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 1, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your $OSSproductName$ $version$ CRDs
+ and delete your installation of $AESproductName$ $version$.
+
+4. **When ready, switch over to $AESproductName$ $version$.**
+
+ You can run $OSSproductName$ $version$ and $AESproductName$ $version$ side-by-side as long as you care
+ to. When you're ready to have $AESproductName$ $version$ handle traffic on its own, switch
+ your original $OSSproductName$ $version$ Service to point to $AESproductName$ $version$. Use
+ `kubectl edit -n emissary service emissary-ingress` and change the `selectors` to:
+
+ ```yaml
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+5. **Install the $productName$ $version$ Ambassador Agent.**
+
+ First, scale the $OSSproductName$ agent to 0:
+
+ ```bash
+ kubectl scale -n emissary deployment/emissary-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent:
+
+ ```bash
+ helm upgrade -n emissary \
+ --set emissary-ingress.agent.enabled=true \
+ $productHelmName$ datawire/$productHelmName$ && \
+ kubectl rollout status -n emissary-ingress deployment/edge-stack -w
+ ```
+
+6. **Finally, enable ACME and filtering in $productName$ $version$.**
+
+ First, scale the $OSSproductName$ Deployment to 0:
+
+ ```bash
+ kubectl scale -n emissary deployment/emissary --replicase=0
+ ```
+
+ Once that's done, enable ACME and filtering in $productName$ $version$:
+
+ ```bash
+ helm upgrade -n emissary \
+ --set emissary-ingress.agent.enabled=true
+ edge-stack datawire/edge-stack && \
+ kubectl rollout status -n emissary deployment/edge-stack -w
+ ````
+
+Congratulations! At this point, $productName$ $version$ is fully running, and
+it's safe to remove the old `emissary` and `emissary-agent` Deployments:
+
+```bash
+kubectl delete -n emissary deployment/emissary deployment/emissary-agent
+```
+
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md
index 685378bab..948704ed1 100644
--- a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-2.5/edge-stack-3.X.md
@@ -79,7 +79,7 @@ You can refer to the [Major changes in $productName$ 3.x](../../../../../../abou
As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read before upgrading.
-$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+$productName$ 3.4 is based on Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
## Migration Steps
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md
index d48526638..d342f175b 100644
--- a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-3.4/edge-stack-3.X.md
@@ -23,7 +23,7 @@ versions is straightforward.
As of $productName$ 3.4.Z, the LightStep tracing driver is no longer supported. To ensure you do not drop any tracing data, be sure to read below before upgrading.
-$productName$ 3.4 has been upgraded from Envoy 1.23 to Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
+$productName$ 3.4 has been upgraded from Envoy 1.23 to Envoy 1.24.1 which removed support for the `LightStep` tracing driver. The team at LightStep and the maintainers of Envoy-Proxy recommend that users instead leverage the OpenTelemetry Collector to send tracing information to LightStep. We have written a guide which can be found here Distributed Tracing with OpenTelemetry and Lightstep that outlines how to set this up. **It is important that you follow this upgrade path prior to upgrading or you will drop tracing data.**
## Migration Steps
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md
new file mode 100644
index 000000000..819df4573
--- /dev/null
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/edge-stack-3.8/edge-stack-3.X.md
@@ -0,0 +1,63 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $productName$ 3.8.Z to $productName$ $version$ (YAML)
+
+
+ This guide covers migrating from $productName$ 3.8.Z to $productName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+Since $productName$'s configuration is entirely stored in Kubernetes resources, upgrading between
+versions is straightforward.
+
+## Migration Steps
+
+Migration is a two-step process:
+
+1. **Install new CRDs.**
+
+ After reviewing the changes in 3.x and confirming that you are ready to upgrade, the process is the same as upgrading minor versions
+ in previous version of $productName$ and does not require the complex migration steps that the migration from 1.x tto 2.x required.
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $productName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $productName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $productName$ $version$.**
+
+ After installing the new CRDs, upgrade $productName$ $version$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml && \
+ kubectl rollout status -n $productNamespace$ deployment/edge-stack -w
+ ```
diff --git a/docs/edge-stack/pre-release/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md
new file mode 100644
index 000000000..0995e73cc
--- /dev/null
+++ b/docs/edge-stack/pre-release/topics/install/upgrade/yaml/emissary-3.9/edge-stack-3.X.md
@@ -0,0 +1,252 @@
+import Alert from '@material-ui/lab/Alert';
+
+# Upgrade $OSSproductName$ $version$ to $AESproductName$ $version$ (YAML)
+
+
+ This guide covers migrating from $OSSproductName$ $version$ to $AESproductName$ $version$. If
+ this is not your exact situation, see the migration
+ matrix.
+
+
+
+ This guide is written for upgrading an installation made without using Helm.
+ If you originally installed with Helm, see the Helm-based
+ upgrade instructions.
+
+
+You can upgrade from $OSSproductName$ to $AESproductName$ with a few simple commands. When you upgrade to $AESproductName$, you'll be able to access additional capabilities such as **automatic HTTPS/TLS termination, Swagger/OpenAPI support, API catalog, Single Sign-On, and more.** For more about the differences between $AESproductName$ and $OSSproductName$, see the [Editions page](/editions).
+
+## Migration Overview
+
+
+ Read the migration instructions below before making any changes to your
+ cluster!
+
+
+The recommended strategy for migration is to run $OSSproductName$ $version$ and $AESproductName$
+$version$ side-by-side in the same cluster. This gives $AESproductName$ $version$
+and $AESproductName$ $version$ access to all the same configuration resources, with some
+important notes:
+
+1. **If needed, you can use labels to further isolate configurations.**
+
+ If you need to prevent your $AESproductName$ $version$ installation from
+ seeing a particular bit of $OSSproductName$ $version$ configuration, you can apply
+ a Kubernetes label to the configuration resources that should be seen by
+ your $AESproductName$ $version$ installation, then set its
+ `AMBASSADOR_LABEL_SELECTOR` environment variable to restrict its configuration
+ to only the labelled resources.
+
+ For example, you could apply a `version-two: true` label to all resources
+ that should be visible to $AESproductName$ $version$, then set
+ `AMBASSADOR_LABEL_SELECTOR=version-two=true` in its Deployment.
+
+2. **$AESproductName$ ACME and `Filter`s will be disabled while $OSSproductName$ is still running.**
+
+ Since $AESproductName$ and $OSSproductName$ share configuration, $AESproductName$ cannot
+ configure its ACME or other filter processors without also affecting $OSSproductName$. This
+ migration process is written to simply disable these $AESproductName$ features to make
+ it simpler to roll back, if needed. Alternate, you can isolate the two configurations
+ as described above.
+
+3. **Be careful to only have one $productName$ Agent running at a time.**
+
+ The $productName$ Agent is responsible for communications between
+ $productName$ and Ambassador Cloud. If multiple versions of the Agent are
+ running simultaneously, Ambassador Cloud could see conflicting information
+ about your cluster.
+
+ The best way to avoid multiple agents when installing with Helm is to use
+ `--set emissary-ingress.agent.enabled=false` to tell Helm not to install a
+ new Agent with $productName$ $version$. Once testing is done, you can switch
+ Agents safely.
+
+4. **Be careful about label selectors on Kubernetes Services!**
+
+ If you have services in $OSSproductName$ 3.X that use selectors that will match
+ Pods from $AESproductName$ $version$, traffic will be erroneously split between
+ $OSSproductName$ 2.4 and $AESproductName$ $version$. The labels used by $AESproductName$
+ $version$ include:
+
+ ```yaml
+ app.kubernetes.io/name: edge-stack
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/part-of: edge-stack
+ app.kubernetes.io/managed-by: getambassador.io
+ product: aes
+ profile: main
+ ```
+
+You can also migrate by [installing $AESproductName$ $version$ in a separate cluster](../../../../migrate-to-3-alternate/).
+This permits absolute certainty that your $OSSproductName$ $version$ configuration will not be
+affected by changes meant for $AESproductName$ $version$, but it is more effort.
+
+## Side-by-Side Migration Steps
+
+Migration is a six-step process:
+
+1. **Install new CRDs.**
+
+ Before installing $productName$ $version$ itself, you need to update the CRDs in
+ your cluster. This is mandatory during any upgrade of $productName$.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-crds.yaml && \
+ kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
+ ```
+
+
+ $AESproductName$ $version$ includes a Deployment in the `emissary-system` namespace
+ called emissary-apiext. This is the APIserver extension
+ that supports converting $OSSproductName$ CRDs between getambassador.io/v2
+ and getambassador.io/v3alpha1. This Deployment needs to be running at
+ all times.
+
+
+
+ If the emissary-apiext Deployment's Pods all stop running,
+ you will not be able to use getambassador.io/v3alpha1 CRDs until restarting
+ the emissary-apiext Deployment.
+
+
+
+ There is a known issue with the emissary-apiext service that impacts all $productName$ 2.x and 3.x users. Specifically, the TLS certificate used by apiext expires one year after creation and does not auto-renew. All users who are running $productName$/$OSSproductName$ 2.x or 3.x with the apiext service should proactively renew their certificate as soon as practical by running kubectl delete --all secrets --namespace=emissary-system to delete the existing certificate, and then restart the emissary-apiext deployment with kubectl rollout restart deploy/emissary-apiext -n emissary-system.
+ This will create a new certificate with a one year expiration. We will issue a software patch to address this issue well before the one year expiration. Note that certificate renewal will not cause any downtime.
+
+
+2. **Install $AESproductName$ $version$.**
+
+ After installing the new CRDs, you need to install $AESproductName$ $version$ itself
+ **in the same namespace as your existing $OSSproductName$ $version$ installation**. It's important
+ to use the same namespace so that the two installations can see the same secrets, etc.
+
+ We publish three manifests for different namespaces. Use only the one that
+ matches the namespace into which you installed $OSSproductName$ $version$:
+
+ - [`aes-emissaryns-migration.yaml`] for the `emissary` namespace;
+ - [`aes-defaultns-migration.yaml`] for the `default` namespace; and
+ - [`aes-ambassadorns-migration.yaml`] for the `ambassador` namespace.
+
+ All three files are set up as follows:
+
+ - They set the `AES_ACME_LEADER_DISABLE` environment variable; you'll enable ACME towards the end of
+ the migration.
+ - They do NOT create any `AuthService` or a `RateLimitService`, since your $OSSproductName$ may have
+ these defined. Again, you'll manage these at the end of migration.
+ - They do NOT set `AMBASSADOR_LABEL_SELECTOR`.
+ - They do NOT install the Ambassador Agent, since there is already an Ambassador Agent running for
+ $OSSproductName$ $version$.
+
+ If any of these do not match your situation, download [`aes-emissaryns-migration.yaml`] and edit it
+ as needed.
+
+ [`aes-emissaryns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$version$/aes-emissaryns-migration.yaml
+ [`aes-defaultns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$version$/aes-defaultns-migration.yaml
+ [`aes-ambassadorns-migration.yaml`]: https://app.getambassador.io/yaml/edge-stack/$version$/aes-ambassadorns-migration.yaml
+
+ Assuming you're using the `emissary` namespace, as was typical for $OSSproductName$ $version$:
+
+ **If you need to set `AMBASSADOR_LABEL_SELECTOR`**, download `aes-emissaryns-migration.yaml` and edit it to
+ do so.
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-emissaryns-migration.yaml && \
+ kubectl rollout status -n emissary deployment/aes -w
+ ```
+
+3. **Test!**
+
+ Your $AESproductName$ $version$ installation should come up running with the configuration
+ resources used by $OSSproductName$ $version$, including `Listener`s and `Host`s.
+
+
+ If you find that your $AESproductName$ $version$ installation and your $OSSproductName$ $version$
+ installation absolutely must have resources that are only seen by one version or the
+ other way, see overview section 1, "If needed, you can use labels to further isolate configurations".
+
+
+ **If you find that you need to roll back**, just reinstall your $OSSproductName$ $version$ CRDs
+ and delete your installation of $AESproductName$ $version$.
+
+4. **When ready, switch over to $AESproductName$ $version$.**
+
+ You can run $OSSproductName$ $version$ and $AESproductName$ $version$ side-by-side as long as you care
+ to. When you're ready to have $AESproductName$ $version$ handle traffic on its own, switch
+ your original $OSSproductName$ $version$ Service to point to $AESproductName$ $version$. Use
+ `kubectl edit -n emissary service emissary-ingress` and change the `selectors` to:
+
+ ```yaml
+ app.kubernetes.io/instance: edge-stack
+ app.kubernetes.io/name: edge-stack
+ profile: main
+ ```
+
+ Repeat using `kubectl edit service ambassador-admin` for the `ambassador-admin`
+ Service.
+
+5. **Install the $productName$ $version$ Ambassador Agent.**
+
+ First, scale the $OSSproductName$ agent to 0:
+
+ ```
+ kubectl scale -n emissary deployment/emissary-ingress-agent --replicas=0
+ ```
+
+ Once that's done, install the new Agent:
+
+ ```
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/aes-emissaryns-agent.yaml && \
+ kubectl rollout status -n emissary deployment/edge-stack-agent -w
+ ```
+
+6. **Finally, enable ACME and filtering in $productName$ $version$.**
+
+
+ Enabling filtering correctly in $productName$ $version$ requires that no
+ AuthService or RateLimitService resources be present; see
+ below for more.
+
+
+ First, make sure that no `AuthService` or `RateLimitService` resources are present; delete
+ these if necessary.
+
+ - If you are currently using an external authentication service that provides functionality
+ you'll still require, turn it into an [`External` `Filter`] (with a [`FilterPolicy`] to
+ direct requests that need it correctly).
+
+ - If you are currently using a `RateLimitService`, you can set up
+ [Edge Stack Rate Limiting] instead.
+
+ [`External` `Filter`]: ../../../../../../howtos/ext-filters#2-configure-aesproductname-authentication
+ [`FilterPolicy`]: ../../../../../../howtos/ext-filters#2-configure-aesproductname-authentication
+ [Edge Stack Rate Limiting]: ../../../../../using/rate-limits
+
+ After making sure no `AuthService` or `RateLimitService` resources are present, scale the
+ $OSSproductName$ Deployment to 0:
+
+ ```bash
+ kubectl scale -n emissary deployment/emissary-ingress --replicase=0
+ ```
+
+ Once that's done, apply resources specific to $AESproductName$:
+
+ ```bash
+ kubectl apply -f https://app.getambassador.io/yaml/edge-stack/$version$/resources-migration.yaml
+ ```
+
+ Then, finally, enable ACME and filtering in $productName$ $version$:
+
+ ```bash
+ kubectl set env -n emissary deployment/aes AES_ACME_LEADER_DISABLE-
+ kubectl rollout status -n emissary deployment/aes -w
+ ````
+
+Congratulations! At this point, $productName$ $version$ is fully running, and
+it's safe to remove the old `emissary` and `emissary-agent` Deployments:
+
+```
+kubectl delete -n emissary deployment/emissary deployment/emissary-agent
+```
+
+You may also want to redirect DNS to the `edge-stack` Service and remove the
+`ambassador` Service.
diff --git a/docs/edge-stack/pre-release/topics/running/aes-extensions/ratelimit.md b/docs/edge-stack/pre-release/topics/running/aes-extensions/ratelimit.md
index 3a758a52f..51d2c6210 100644
--- a/docs/edge-stack/pre-release/topics/running/aes-extensions/ratelimit.md
+++ b/docs/edge-stack/pre-release/topics/running/aes-extensions/ratelimit.md
@@ -25,8 +25,16 @@ metadata:
namespace: ambassador
spec:
service: 127.0.0.1:8500
+ failure_mode_deny: false # when set to true envoy will return 500 error when unable to communicate with RateLimitService
+ grpc:
+ use_resource_exhausted_code: true # default is false
```
+- `failure_mode_deny` By default, $productName$ will fail open when unable to communicate with the service due to it becoming unvailable or due to timeouts. When this happens the upstream service that is being protected by a rate limit may be overloaded due to this behavior. When set to `true` $productName$ will be configured to return a `500` status code when it is unable to communicate with the RateLimit service and will fail closed by rejecting request to the upstream service.
+- `grpc` contains settings for grpc connections
+ - `use_resource_exhausted_code` By default, $productName$ will return an `UNAVAILABLE` gRPC code when a request is rate limited.
+ When set to `true`, this field will cause $productName$ will return a `RESOURCE_EXHAUSTED` gRPC code instead.
+
This configures Envoy to send requests that are labeled for rate limiting to the
extension process running on port 8500. The rate limiting extension will then
use that request to count against any `RateLimit` whose pattern matches the
@@ -40,7 +48,7 @@ Configuration of this extension is managed via environment variables.
variables available for configuration. This document highlights the ones used
by the rate limiting extension.
-#### Redis
+### Redis
The rate limiting extension relies heavily on redis for writing and reading
counters for the different `RateLimit` patterns.
@@ -50,7 +58,7 @@ Redis.
See the [Redis documentation](../../aes-redis) for information on Redis tuning.
-### REDIS_PERSECOND
+#### REDIS_PERSECOND
If `REDIS_PERSECOND` is true, a second Redis connection pool is created (to a
potentially different Redis instance) that is only used for per-second
@@ -65,7 +73,7 @@ preview mode.
#### `LOCAL_CACHE_SIZE_IN_BYTES`
-* Only available if `AES_RATELIMIT_PREVIEW: "true`.
+**Only available if `AES_RATELIMIT_PREVIEW: "true`.**
The AES rate limit extension can optionally cache over-the-limit keys so it does
not need to read the redis cache again for requests with labels that are already
@@ -76,7 +84,7 @@ caching.
#### `NEAR_LIMIT_RATIO`
-* Only available if `AES_RATELIMIT_PREVIEW: "true"`
+**Only available if `AES_RATELIMIT_PREVIEW: "true"`**
Adjusts the ratio used by the `near_limit` statistic for tracking requests that
are "near the limit".
diff --git a/docs/edge-stack/pre-release/topics/running/aes-redis.md b/docs/edge-stack/pre-release/topics/running/aes-redis.md
index 22c70125a..fe72b03be 100644
--- a/docs/edge-stack/pre-release/topics/running/aes-redis.md
+++ b/docs/edge-stack/pre-release/topics/running/aes-redis.md
@@ -51,7 +51,7 @@ Specifies whether to skip certificate verification when
using TLS to talk to Redis.
Consider [installing the self-signed certificate for your Redis in to the
-Ambassador Edge Stack container](../../using/filters/#installing-self-signed-certificates)
+Ambassador Edge Stack container](../../using/filters/#filters-using-self-signed-certificates)
in order to leave certificate verification on.
## Redis authentication (auth)
diff --git a/docs/edge-stack/pre-release/topics/running/environment.md b/docs/edge-stack/pre-release/topics/running/environment.md
index 03c607c68..54a05c9bb 100644
--- a/docs/edge-stack/pre-release/topics/running/environment.md
+++ b/docs/edge-stack/pre-release/topics/running/environment.md
@@ -5,7 +5,8 @@ Use the following variables for the environment of your $productName$ container:
| Variable | Default value | Value type |
|----------------------------------------------------------------------------------------------------------- |-----------------------------------------------------|-------------------------------------------------------------------------------|
| [`AMBASSADOR_ID`](#ambassador_id) | `[ "default" ]` | List of strings |
-| [`AES_LOG_LEVEL`](#aes_log_level) | `warn` | Log level |
+| [`AES_LOG_LEVEL`](#aes_log_level)
+| [`AES_DISABLE_LICENSE_USAGE_REPORTING`](#aes_disable_license_usage_reporting) | `false` | Any: `true`=true and unset is false |
| [`AGENT_CONFIG_RESOURCE_NAME`](#agent_config_resource_name) | `ambassador-agent-cloud-token` | String |
| [`AMBASSADOR_AMBEX_NO_RATELIMIT`](#ambassador_ambex_no_ratelimit) | `false` | Boolean: `true`=true, any other value=false |
| [`AMBASSADOR_AMBEX_SNAPSHOT_COUNT`](#ambassador_ambex_snapshot_count) | `30` | Integer |
@@ -123,6 +124,12 @@ Log level names are case-insensitive.
[More information](../../running/running#log-levels-and-debugging)
+### `AES_DISABLE_LICENSE_USAGE_REPORTING`
+
+Usage data is collected and sent to Ambassador Labs servers on regular intervals. If you are using an in-cluster AirGapped license and wish to disable this, then setting `AES_DISABLE_LICENSE_USAGE_REPORTING` to true will prevent $productName$ from reporting license usage data. If your cluster is using a Cloud license then users are required to send this data and cannot disable it.
+
+[More license information](../../../topics/using/licenses)
+
### `AGENT_CONFIG_RESOURCE_NAME`
Allows overriding the default config_map/secret that is used for extracting the CloudToken for connecting with Ambassador cloud. It allows all
@@ -150,7 +157,7 @@ to reveal the namespace name nor $productName$ ID itself. $productName$ needs RB
if not granted this permission it will generate a UUID based only on the $productName$ ID. To disable cluster ID generation entirely, set the environment variable
`AMBASSADOR_CLUSTER_ID` to a UUID that will be used for the cluster ID.
-[More information](../../running/running#ambassador-edge-stack-update-checks-scout)
+[More information](../../running/running#ambassador-edge-stack-usage-telemetry-scout)
### `AMBASSADOR_CONFIG_BASE_DIR`
@@ -162,7 +169,7 @@ Controls where $productName$ will store snapshots. By default, the latest config
To completely disable feature reporting, set the environment variable `AMBASSADOR_DISABLE_FEATURES` to any non-empty value.
-[More information](../../running/running/#ambassador-edge-stack-update-checks-scout)
+[More information](../../running/running/#ambassador-edge-stack-usage-telemetry-scout)
### `AMBASSADOR_DRAIN_TIME`
@@ -272,14 +279,12 @@ Because the DogStatsD protocol is slightly different than the normal StatsD prot
### `SCOUT_DISABLE`
-$productName$ integrates Scout, a service that periodically checks with Datawire servers to advise of available updates. Scout also sends anonymized usage
-data and the $productName$ version. This information is important to us as we prioritize test coverage, bug fixes, and feature development. Note that the $productName$ will
-run regardless of the status of Scout.
+$productName$ integrates Scout, a service that periodically checks with Ambassador Labs servers to sends anonymized usage data and the $productName$ version. This information is important to us as we prioritize test coverage, bug fixes, and feature development. Note that the $productName$ will run regardless of the status of Scout.
-We do not recommend you disable Scout, since we use this mechanism to notify users of new releases (including critical fixes and security issues). This check can be disabled by setting
+We do not recommend you disable Scout. This check can be disabled by setting
the environment variable `SCOUT_DISABLE` to `1` in your $productName$ deployment.
-[More information](../../running/running#ambassador-edge-stack-update-checks-scout)
+[More information](../../running/running#ambassador-edge-stack-usage-telemetry-scout)
### `STATSD_ENABLED`
diff --git a/docs/edge-stack/pre-release/topics/using/filters/apikeys.md b/docs/edge-stack/pre-release/topics/using/filters/apikeys.md
index b65bb9afe..7cd9e3479 100644
--- a/docs/edge-stack/pre-release/topics/using/filters/apikeys.md
+++ b/docs/edge-stack/pre-release/topics/using/filters/apikeys.md
@@ -1,35 +1,125 @@
import Alert from '@material-ui/lab/Alert';
-# API Keys Filter
-
-The API Keys filter validates API Keys present in the HTTP header. The list of authorized API Keys is defined directly in a secret.
-
-## API Keys global arguments
-
-```yaml
----
-apiVersion: getambassador.io/v3alpha1
-kind: Filter
-metadata:
- name: "example-apikeys-filter"
- namespace: "example-namespace"
-spec:
- APIKey:
- httpHeader: "x-my-api-key-header" # optional; default is X-API-Key
- keys:
- - secretName: "my-secret-api-keys"
----
-apiVersion: v1
-kind: Secret
-metadata:
- namespace: ambassador
- name: my-secret-api-keys
-data:
- key-one: bXktZmlyc3QtYXBpLWtleQ==
- key-two: bXktc2Vjb25kLWFwaS1rZXk=
-```
-
- - `httpHeader` is the header used to do the API Keys validation.
-
- - `keys`: A list of API keys that will be used for the validation. A list of keys can be defined using a secret or you can define a standalone key directly in the filter resource.
+# Using The API Keys Filter
+The `APIKey Filter` validates API Keys present in HTTP headers. The list of authorized API Keys is defined directly in a Secret.
+If an incoming request does not have the header specified by the `APIKey Filter` or it does not contain one of the key values
+configured by the `Filter` then the request is denied.
+
+
+
+See the [API Key Filter API reference][] for an overview of all the supported fields.
+
+## APIKey Filter Quickstart
+
+1. Come up with an API Key value to use. For this example, we're going to use the string `example-apikey-value`
+
+2. Convert the API Key value to [base64][].
+
+ You can do this however you prefer, such as with an online tool like [base64encode.org][] or with the terminal:
+
+ ```console
+ $ echo -n example-api-key-value | base64
+ ZXhhbXBsZS1hcGkta2V5LXZhbHVl
+ ```
+
+3. Create an [APIKey Filter][] with the encoded API Key from above:
+
+ ```yaml
+ kubectl apply -f -<
+ If you want to create more APIKeys, you can continue to add them to your secret. The keys (key-1 in the example) used in the Secret do not matter, so you can name them whatever helps you keep track of the associated API Keys.
+
+
+4. Create a [FilterPolicy resource][] to use the `Filter` created above
+
+ ```yaml
+ kubectl apply -f -< GET /backend/ HTTP/1.1
+ > Accept: */*
+ >
+ < HTTP/1.1 403 Forbidden
+ < content-type: application/json
+ < server: envoy
+ <
+ {"message":"API key not found","requestId":"","statusCode":403}
+ ```
+
+
+ The request was denied because the header was not found, but it will also be denied if you send the correct header with an invalid API Key.
+
+
+6. Send a request with the APIKey header and value.
+
+ ```console
+ $ curl -ki http://$GATEWAY_HOST/backend/ -H "example-key-header: example-api-key-value"
+
+ > GET /backend/ HTTP/1.1
+ > Accept: */*
+ > example-key-header: example-api-key-value
+ >
+ < HTTP/1.1 200 OK
+ < content-type: application/json
+ < server: envoy
+ <
+ {
+ "server": "buoyant-raspberry-ju848o1i",
+ "quote": "A principal idea is omnipresent, much like candy.",
+ "time": "2023-08-04T03:40:45.594594388Z"
+ }
+ ```
+
+
+ Success! Your requests are now validated against an APIKey Filter and will be denied if they do not supply a valid API key!
+
+
+[API Key Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-apikey
+[APIKey Filter]: ../../../../custom-resources/getambassador/v3alpha1/filter-apikey
+[FilterPolicy resource]: ../../../../custom-resources/getambassador/v3alpha1/filterpolicy
+[base64encode.org]: https://www.base64encode.org/
+[base64]: https://en.wikipedia.org/wiki/Base64
diff --git a/docs/edge-stack/pre-release/topics/using/filters/external.md b/docs/edge-stack/pre-release/topics/using/filters/external.md
index 9d24229f6..5e8e587e9 100644
--- a/docs/edge-stack/pre-release/topics/using/filters/external.md
+++ b/docs/edge-stack/pre-release/topics/using/filters/external.md
@@ -1,39 +1,11 @@
-# External Filter
+# Using The External Filter
The `External` `Filter` calls out to an external service speaking the
[`ext_authz` protocol](../../../running/services/ext-authz), providing
a highly flexible interface to plug in your own authentication,
authorization, and filtering logic.
-## Example
-
-```yaml
----
-apiVersion: getambassador.io/v3alpha1
-kind: Filter
-metadata:
- name: "my-filter"
- namespace: "my-namespace"
-spec:
- External:
- auth_service: "https://example-auth:3000"
- proto: http
- timeout_ms: 5000
- include_body:
- max_bytes: 4096
- allow_partial: true
- status_on_error:
- code: 403
- failure_mode_allow: false
-
- # proto: http only
- path_prefix: "/path"
- allowed_request_headers:
- - "x-allowed-input-header"
- allowed_authorization_headers:
- - "x-allowed-output-header"
- add_linkerd_headers: false
-```
+
The `External` spec is identical to the [`AuthService`
spec](../../../running/services/auth-service), with the following
@@ -49,42 +21,9 @@ exceptions:
* `External` `Filters` lack the `stats_name`, and
`add_auth_headers` fields that `AuthServices` have.
-## Fields
-
-`auth_service` is the only required field, all others are optional.
-
-| Attribute | Default value | Description |
-| ---------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `auth_service` | (none; a value is required) | Identifies the external auth service to talk to. The format of this field is `scheme://host:port` where `scheme://` and `:port` are optional. The scheme-part, if present, must be either `http://` or `https://`; if the scheme-part is not present, it behaves as if `http://` is given. The scheme-part influences the default value of the `tls` field and the default value of the port-part. The host-part must be the [namespace-qualified DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services) of the service you want to use for authentication. |
-| `tls` | `true` if `auth_service` starts with "https://" | Controls whether to use TLS or cleartext when speaking to the external auth service. The default is based on the scheme-part of the `auth_service`. |
-| `tlsConfig` | none; optional | Configure tls settings between $productName$ and the configured AuthService. See [`Configure TLS Settings`](#configuring-tls-settings). |
-| `proto` | `http` | Specifies which variant of the [`ext_authz` protocol](../../../running/services/ext-authz) to use when communicating with the external auth service. Valid options are `http` or `grpc`. |
-| `timeout_ms` | `5000` | The total maximum duration in milliseconds for the request to the external auth service, before triggering `status_on_error` or `failure_mode_allow`. |
-| `include_body` | `null` | Controls how much to buffer the request body to pass to the external auth service, for use cases such as computing an HMAC or request signature. If `include_body` is `null` or unset, then the request body is not buffered at all, and an empty body is passed to the external auth service. If `include_body` is not `null`, the `max_bytes` and `allow_partial` subfields are required. Unfortunately, in order for `include_body` to function properly, the `AuthService` in [`aes.yaml`](https://app.getambassador.io/yaml/edge-stack/$version$/aes.yaml) must be edited to have `include_body` set with `max_bytes` greater than the largest `max_bytes` used by any `External` `Filter` (so if an `External` `Filter` has `max_bytes: 4096`, then the `AuthService` will need `max_bytes: 4097`), and `allow_partial: true`. |
-| `include_body.max_bytes` | (none; a value is required if `include_body` is not `null`) | Controls the amount of body data that is passed to the external auth service. |
-| `include_body.allow_partial` | (none; a value is required if `include_body` is not `null`) | Controls what happens to requests with bodies larger than `max_bytes`. If `allow_partial` is `true`, the first `max_bytes` of the body are sent to the external auth service. If `false`, the message is rejected with HTTP 413 ("Payload Too Large"). |
-| `status_on_error.code` | `403` | Controls the status code returned when unable to communicate with external auth service. This is ignored if `failure_mode_allow: true`. |
-| `failure_mode_allow` | `false` | Controls whether to allow or reject requests when there is an error communicating with the external auth service; a value of `true` allows the request through to the upstream backend service, a value of `false` returns a `status_on_error.code` response to the client. |
-| `protocol_version` | `v2` | Indicates the version of the transport protocol that the External Filter is using. This is only applicable to External Filters using `proto: grpc`. Allowed values are `v3` and `v2`(defualt). `protocol_version` was used in previous versions of $productName$ to note the protocol used by the gRPC service for the External Filter. $productName$ 3.x is running an updated version of Envoy that has dropped support for the `v2` protocol, so starting in 3.x, if `protocol_version` is not specified, the default value of `v2` will cause an error to be posted and a static response will be returned. Therefore, you must set it to `protocol_version: v3`. If upgrading from a previous version, you will want to set it to `v3` and ensure it is working before upgrading to Emissary-ingress 3.Y. The default value for `protocol_version` remains `v2` in the `getambassador.io/v3alpha1` CRD specifications to avoid making breaking changes outside of a CRD version change. Future versions of CRD's will deprecate it. |
+
-The following fields are only used if `proto` is set to `http`. They
-are ignored if `proto` is `grpc`.
-
-| Attribute | Default value | Description |
-| ------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `path_prefix` | `""` | Prepends a string to the request path of the request when sending it to the external auth service. By default this is empty, and nothing is prepended. For example, if the client makes a request to `/foo`, and `path_prefix: /bar`, then the path in the request made to the external auth service will be `/foo/bar`. |
-| `allowed_request_headers` | `[]` | Lists the headers (case-insensitive) that are copied from the incoming request to the request made to the external auth service. In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Cookie`, `From`, `Proxy-Authorization`, `User-Agent`, `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto`. |
-| `allowed_authorization_headers` | `[]` | Lists the headers (case-insensitive) that are copied from the response from the external auth service to the request sent to the upstream backend service (if the external auth service indicates that the request to the upstream backend service should be allowed). In addition to the headers listed in this field, the following headers are always included: `Authorization`, `Location`, `Proxy-Authenticate`, `Set-cookie`, `WWW-Authenticate` |
-| `add_linkerd_headers` | `false` | When true, in the request to the external auth service, adds an `l5d-dst-override` HTTP header that is set to the hostname and port number of the external auth service. |
-
-
-The following fields are only used if `proto` is set to `grpc`. They
-are ignored if `proto` is `http`.
-
-
-| Attribute | Default value | Description |
-| ------------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `protocol_version` | `v2` | Indicates the version of the transport protocol that the External Filter is using. This is only applicable to External Filters using `proto: grpc`. When left unset or set to `v2` $productName$ will automatically convert between the `v2` protocol used by the External Filter and the `v3` protocol that is used by the `AuthService` that ships with $productName$. When this field is set to `v3` then no conversion between $productName$ and the `AuthService` will take place as it can speak `v3` natively with $productName$'s `AuthService`. |
+See the [External Filter API reference][] for an overview of all the supported fields.
## Tracing Header Propagation
@@ -113,22 +52,17 @@ spec:
## Configuring TLS Settings
-When an `ExternalFilter` has the `auth_service` field configured with a URL that starts with `https://` then $productName$ will attempt to communicate with the AuthService over a TLS connection. The following configurations are supported:
+When an `External Filter` has the `auth_service` field configured with a URL that starts with `https://` then $productName$
+will attempt to communicate with the `AuthService` over a TLS connection. The following configurations are supported:
1. Verify server certificate with host CA Certificates - *default when `tls: true`*
2. Verify server certificate with provided CA Certificate
3. Mutual TLS between client and server
-Overall, these new configuration options enhance the security of the communications between $productName$ and your `ExternalFilter` by providing a way to verify the server's certificate, allowing customization of the trust verification process, and enabling mutual TLS (mTLS) between $productName$ and the `ExternalFilter` service. By employing these security measures, users can have greater confidence in the authenticity, integrity, and confidentiality of their filter's actions, especially if it interacts with any sensitive information.
-
-The following settings are provided for configuring the `tlsConfig`:
-
-| Attribute | Sub-Field | Default Value | Description |
-| --------------- | ------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `caCertificate` | | | Configures $productName$ to use the provided CA certifcate to verify the server provided certificate. |
-| | `fromSecret` | secret `namespace` defaults to Filter namespace if not set. | Provide the `name` and `namespace` (optional) of an `Opaque` [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/#secret-types) that contains the `tls.crt` key with the CA Certificate. |
-| `certificate` | | | Configures $productName$ to use the provided certificate to present to the server when connecting. |
-| | `fromSecret` | secret `namespace` defaults to Filter namespace if not set. | Provide the `name` and `namespace` (optional) of a `kubernetes.io/tls` [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/#secret-types) that contains the private key and public certificate that will be presented to the AuthService. |
+Overall, these new configuration options enhance the security of the communications between $productName$ and your `External Filter` by providing a way to
+verify the server's certificate, allowing customization of the trust verification process, and enabling mutual TLS (mTLS) between $productName$ and the
+`External Filter` service. By employing these security measures, users can have greater confidence in the authenticity, integrity,
+and confidentiality of their filter's actions, especially if it interacts with any sensitive information.
### Example - Verify Server with Custom CA Certificate
@@ -184,7 +118,6 @@ As of $productName$ 3.4.0, the following metrics for External Filters are availa
| `ambassador_edge_stack_external_filter_rq_class` | Counter (with labels) | Aggregated counter of response code classes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter. |
| `ambassador_edge_stack_external_filter_rq_status` | Counter (with labels) | Counter of response codes returned to downstream clients from Ambassador Edge Stack External Filters. Includes requests that are denied by an inability to connect to the External Filter. |
-
An example of what the metrics may look like can be seen below
```
@@ -219,7 +152,6 @@ ambassador_edge_stack_external_filter_rq_status{status="501"} 5
ambassador_edge_stack_external_handler_error 0
```
-
## Transport Protocol Migration
> **Note:** The following information is only applicable to External Filters using `proto: grpc`
@@ -229,18 +161,22 @@ of it should migrate to `v3` before support for `v2` is removed in a future rele
The following imports simply need to be updated to migrate an External Filter
`v2` Imports:
-```
- envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
- envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
- envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
+
+```go
+ envoyCoreV2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core"
+ envoyAuthV2 "github.com/datawire/ambassador/pkg/api/envoy/service/auth/v2"
+ envoyType "github.com/datawire/ambassador/pkg/api/envoy/type"
```
`v3` Imports:
-```
- envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
- envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
- envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
+
+```go
+ envoyCoreV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/config/core/v3"
+ envoyAuthV3 "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v3"
+ envoyType "github.com/datawire/ambassador/v2/pkg/api/envoy/type/v3"
```
In the [datawire/sample-external-service repository](https://github.com/datawire/Sample-External-Service), you can find examples of an External Filter using both the
`v2` transport protocol as well as `v3` along with deployment instructions for reference. The External Filter in this repo does not perform any authorization and is instead meant to serve as a reference for the operations that an External can make use of.
+
+[External Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-external
diff --git a/docs/edge-stack/pre-release/topics/using/filters/index.md b/docs/edge-stack/pre-release/topics/using/filters/index.md
index 4bf92f5c4..cefae8b4b 100644
--- a/docs/edge-stack/pre-release/topics/using/filters/index.md
+++ b/docs/edge-stack/pre-release/topics/using/filters/index.md
@@ -1,115 +1,28 @@
import Alert from '@material-ui/lab/Alert';
-# Filters and authentication
+# Using Filters and FilterPolicies
-Filters are used to extend the Ambassador Edge Stack to modify or intercept a request before sending to your backend service. The most common use case for Filters is authentication, and Edge Stack includes a number of built-in filters for this purpose. Edge Stack also supports developing custom filters.
-
-Filters are managed using a FilterPolicy resource. The FilterPolicy resource specifies a particular host or URL to match, along with a set of filters to run when an request matches the host/URL.
+Filters are used to extend the Ambassador Edge Stack to modify or intercept a request before sending to your
+backend service. The most common use case for Filters is authentication, and Edge Stack includes a number
+of built-in filters for this purpose. Edge Stack also supports developing custom filters.
## Filter types
Edge Stack supports the following filter types:
-* [JWT](jwt) - validates JSON Web Tokens
-* [OAuth2](oauth2) - performs OAuth2 authorization against an identity provider implementing [OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html).
-* [Plugin](plugin) - allows users to write custom Filters in Go that run as part of the Edge Stack container
-* [External](external) - allows users to call out to other services for request processing. This can include both custom services (in any language) or third party services.
-* [API Keys](apikeys) - validates API Keys present in a custom HTTP header
+- [JWT][]: Validates JSON Web Tokens
+- [OAuth2][]: Performs OAuth2 authorization against an identity provider implementing [OIDC Discovery][].
+- [Plugin][]: Allows users to write custom Filters in Go that run as part of the Edge Stack container
+- [External][]: Allows users to call out to other services for request processing. This can include both custom services (in any language) or third party services.
+- [API Keys][]: Validates API Keys present in a custom HTTP header
## Managing Filters
-Filters are created with the Filter resource type, which contains global arguments to that filter. Which Filter(s) to use for which HTTP requests is then configured in FilterPolicy resources, which may contain path-specific arguments to the filter.
-
-### Filter definition
-
-Filters are created as Filter resources. The body of the resource spec depends on the filter type:
-
-```yaml
----
-apiVersion: getambassador.io/v3alpha1
-kind: Filter
-metadata:
- name: "string" # required; this is how to refer to the Filter in a FilterPolicy
- namespace: "string" # optional; default is the usual `kubectl apply` default namespace
-spec:
- ambassador_id: # optional; default is ["default"]
- - "string"
- ambassador_id: "string" # no need for a list if there's only one value
- FILTER_TYPE:
- GLOBAL_FILTER_ARGUMENTS
-```
-
-### FilterPolicy definition
+Filters are created with the `Filter` resource type, which contains global arguments to that filter.
+Which Filter(s) to use for which HTTP requests is then configured in [FilterPolicy resources][],
+which may contain path-specific arguments to the filter.
-FilterPolicy resources specify which filters (if any) to apply to
-which HTTP requests.
-
-```yaml
----
-apiVersion: getambassador.io/v3alpha1
-kind: FilterPolicy
-metadata:
- name: "example-filter-policy"
- namespace: "example-namespace"
-spec:
- rules:
- - host: "glob-string"
- path: "glob-string"
- filters: # optional; omit or set to `null` or `[]` to apply no filters to this request
- - name: "string" # required
- namespace: "string" # optional; default is the same namespace as the FilterPolicy
- ifRequestHeader: # optional; default to apply this filter to all requests matching the host & path
- name: "string" # required
- negate: bool # optional; default is false
- # It is invalid to specify both "value" and "valueRegex".
- value: "string" # optional; default is any non-empty string
- valueRegex: "regex-string" # optional; default is any non-empty string
- onDeny: "enum-string" # optional; default is "break"
- onAllow: "enum-string" # optional; default is "continue"
- arguments: DEPENDS # optional
-```
-
-Rule configuration values include:
-
-| Value | Example | Description |
-| ----- | ------- | ----------- |
-| `host` | `*`, `foo.com` | The Host that a given rule should match |
-| `path` | `/foo/url/` | The URL path that a given rule should match to |
-| `filters` | `name: keycloak` | The name of a given filter to be applied|
-
-The wildcard `*` is supported for both `path` and `host`.
-
-The type of the `arguments` property is dependent on which Filter type is being referred to; see the "Path-Specific Arguments" documentation for each Filter type.
-
-When multiple Filters are specified in a rule:
-
- * The filters are gone through in order
- * Each filter may either:
- * return a direct HTTP *response*, intended to be sent back to the requesting HTTP client (normally *denying* the request from being forwarded to the upstream service) OR
- * return a modification to make to the HTTP *request* before sending it to other filters or the upstream service (normally *allowing* the request to be forwarded to the upstream service with modifications).
- * If a filter has an `ifRequestHeader` setting, the filter is skipped
- unless the request (including any modifications made by earlier
- filters) has the HTTP header field `name`
- set to (or not set to if `negate: true`):
- * a non-empty string if neither `value` nor `valueRegex` are set
- * the exact string `value` (case-sensitive) (if `value` is set)
- * a string that matches the regular expression `valueRegex` (if
- `valueRegex` is set). This uses [RE2][] syntax (always, not
- obeying [`regex_type`][] in the Ambassador module) but does not
- support the `\C` escape sequence.
- * `onDeny` identifies what to do when the filter returns an "HTTP response":
- * `"break"`: End processing, and return the response directly to
- the requesting HTTP client. Later filters are not called. The request is not forwarded to the upstream service.
- * `"continue"`: Continue processing. The request is passed to the
- next filter listed; or if at the end of the list, it is forwarded to the upstream service. The HTTP response returned from the filter is discarded.
- * `onAllow` identifies what to do when the filter returns a
- "modification to the HTTP request":
- - `"break"`: Apply the modification to the request, then end filter processing, and forward the modified request to the upstream service. Later filters are not called.
- - `"continue"`: Continue processing. Apply the request modification, then pass the modified request to the next filter
- listed; or if at the end of the list, forward it to the upstream service.
- * Modifications to the request are cumulative; later filters have access to _all_ headers inserted by earlier filters.
-
-#### FilterPolicy example
+## Using a Filter in a FilterPolicy
In the example below, the `param-filter` Filter Plugin is loaded and configured to run on requests to `/httpbin/`.
@@ -152,7 +65,7 @@ spec:
Edge Stack will choose the first FilterPolicy rule that matches the incoming request. As in the above example, you must list your rules in the order of least to most generic.
-#### Multiple domains
+## FilterPolicies With Multiple Domains
In this example, the `foo-keycloak` filter is used for requests to `foo.bar.com`, while the `example-auth0` filter is used for requests to `example.com`. This configuration is useful if you are hosting multiple domains in the same cluster.
@@ -173,7 +86,7 @@ spec:
- name: example-auth0
```
-## Installing self-signed certificates
+## Filters Using Self-Signed Certificates
The JWT and OAuth2 filters speak to other services over HTTP or HTTPS. If those services are configured to speak HTTPS using a self-signed certificate, attempting to talk to them will result in an error mentioning `ERR x509: certificate signed by unknown authority`. You can fix this by installing that self-signed certificate into the AES container by copying the certificate to `/usr/local/share/ca-certificates/` and then running `update-ca-certificates`. Note that the `aes` image sets `USER 1000` but `update-ca-certificates` needs to be run as root.
@@ -186,3 +99,11 @@ COPY ./my-certificate.pem /usr/local/share/ca-certificates/my-certificate.crt
RUN update-ca-certificates
USER 1000
```
+
+[JWT]: ./jwt
+[OAuth2]: ./oauth2
+[Plugin]: ./plugin
+[External]: ./external
+[API Keys]: ./apikeys
+[FilterPolicy resources]: ../../../custom-resources/getambassador/v3alpha1/filterpolicy
+[OIDC Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
diff --git a/docs/edge-stack/pre-release/topics/using/filters/jwt.md b/docs/edge-stack/pre-release/topics/using/filters/jwt.md
index 562314d34..bae8daa16 100644
--- a/docs/edge-stack/pre-release/topics/using/filters/jwt.md
+++ b/docs/edge-stack/pre-release/topics/using/filters/jwt.md
@@ -1,138 +1,15 @@
import Alert from '@material-ui/lab/Alert';
-# JWT Filter
+# Using The JWT Filter
-The JWT filter type performs JWT validation on a [bearer token](https://tools.ietf.org/html/rfc6750) present in the HTTP header. If the bearer token JWT doesn't validate, or has insufficient scope, an RFC 6750-complaint error response with a `WWW-Authenticate` header is returned. The list of acceptable signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in `jwksURI`. Only RSA and `none` algorithms are supported.
+The JWT filter type performs JWT validation on a [bearer token](https://tools.ietf.org/html/rfc6750) present in the HTTP header.
+If the bearer token JWT doesn't validate, or has insufficient scope, an RFC 6750-complaint error response with a `WWW-Authenticate`
+header is returned. The list of acceptable signing keys is loaded from a JWK Set that is loaded over HTTP, as specified in
+`jwksURI`. Only RSA and `none` algorithms are supported.
-## JWT global arguments
+
-```yaml
----
-apiVersion: getambassador.io/v3alpha1
-kind: Filter
-metadata:
- name: "example-jwt-filter"
- namespace: "example-namespace"
-spec:
- JWT:
- jwksURI: "url-string" # required, unless the only validAlgorithm is "none"
- insecureTLS: bool # optional; default is false
- renegotiateTLS: "enum-string" # optional; default is "never"
- validAlgorithms: # optional; default is "all supported algos except for 'none'"
- - "RS256"
- - "RS384"
- - "RS512"
- - "none"
-
- audience: "string" # optional, unless `requireAudience: true`
- requireAudience: bool # optional; default is false
-
- issuer: "url-string" # optional, unless `requireIssuer: true`
- requireIssuer: bool # optional; default is false
-
- requireExpiresAt: bool # optional; default is false
- leewayForExpiresAt: "duration" # optional; default is "0"
-
- requireNotBefore: bool # optional; default is false
- leewayForNotBefore: "duration" # optional; default is "0"
-
- requireIssuedAt: bool # optional; default is false
- leewayForIssuedAt: "duration" # optional; default is "0"
-
- maxStale: # optional; default is "0"
-
- injectRequestHeaders: # optional; default is []
- - name: "header-name-string" # required
- value: "go-template-string" # required
-
- errorResponse: # optional
- contentType: "string" # deprecated; use 'headers' instead
- realm: "string" # optional; default is "{{.metadata.name}}.{{.metadata.namespace}}"
- headers: # optional; default is [{name: "Content-Type", value: "application/json"}]
- - name: "header-name-string" # required
- value: "go-template-string" # required
- bodyTemplate: "string" # optional; default is `{{ . | json "" }}`
-```
-
- - `insecureTLS` disables TLS verification for the cases when `jwksURI` begins with `https://`. This is discouraged in favor of either using plain `http://` or [installing a self-signed certificate](../#installing-self-signed-certificates).
- - `renegotiateTLS` allows a remote server to request TLS renegotiation. Accepted values are "never", "onceAsClient", and "freelyAsClient".
- - `leewayForExpiresAt` allows tokens expired by this much to be used;
- to account for clock skew and network latency between the HTTP
- client and the Ambassador Edge Stack.
- - `leewayForNotBefore` allows tokens that shouldn't be used until
- this much in the future to be used; to account for clock skew
- between the HTTP client and the Ambassador Edge Stack.
- - `leewayForIssuedAt` allows tokens issued this much in the future to
- be used; to account for clock skew between the HTTP client and
- the Ambassador Edge Stack.
- - `maxStale` How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also ignores the `no-store` and `no-cache` Cache-Control directives on responses. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Note that if you are reusing the same `authorizationURL` and `jwksURI` across different OAuth and JWT filters respectively, then you MUST set `maxStale` as a consistent value on each filter to get predictable caching behavior.
- - `injectRequestHeaders` injects HTTP header fields in to the request before sending it to the upstream service; where the header value can be set based on the JWT value. The value is specified as a [Go `text/template`][] string, with the following data made available to it:
-
- * `.token.Raw` → `string` the raw JWT
- * `.token.Header` → `map[string]interface{}` the JWT header, as parsed JSON
- * `.token.Claims` → `map[string]interface{}` the JWT claims, as parsed JSON
- * `.token.Signature` → `string` the token signature
- * `.httpRequestHeader` → [`http.Header`][] a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
-
- Also available to the template are the [standard functions available
- to Go `text/template`s][Go `text/template` functions], as well as:
-
- * a `hasKey` function that takes the a string-indexed map as arg1,
- and returns whether it contains the key arg2. (This is the same
- as the [Sprig function of the same name][Sprig `hasKey`].)
-
- * a `doNotSet` function that causes the result of the template to
- be discarded, and the header field to not be adjusted. This is
- useful for only conditionally setting a header field; rather
- than setting it to an empty string or `""`. Note that
- this does _not_ unset an existing header field of the same name;
- in order to prevent the untrusted client from being able to
- spoof these headers, use a [Lua script][Lua Scripts] to remove
- the client-supplied value before the Filter runs. See below for
- an example. Not sanitizing the headers first is a potential
- security vulnerability.
-
- Any headers listed will override (not append to) the original request header with that name.
- - `errorResponse` allows templating the error response, overriding the default json error format. Make sure you validate and test your template, not to generate server-side errors on top of client errors.
- * `contentType` is deprecated, and is equivalent to including a
- `name: "Content-Type"` item in `headers`.
- * `realm` allows specifying the realm to report in the `WWW-Authenticate` response header.
- * `headers` sets extra HTTP header fields in the error response. The value is specified as a [Go `text/template`][] string, with the same data made available to it as `bodyTemplate` (below). It does not have access to the `json` function.
- * `bodyTemplate` specifies body of the error; specified as a [Go `text/template`][] string, with the following data made available to it:
-
- * `.status_code` → `integer` the HTTP status code to be returned
- * `.httpStatus` → `integer` an alias for `.status_code` (hidden from `{{ . | json "" }}`)
- * `.message` → `string` the error message string
- * `.error` → `error` the raw Go `error` object that generated `.message` (hidden from `{{ . | json "" }}`)
- * `.error.ValidationError` → [`jwt.ValidationError`][] the JWT validation error, will be `nil` if the error is not purely JWT validation (insufficient scope, malformed or missing `Authorization` header)
- * `.request_id` → `string` the Envoy request ID, for correlation (hidden from `{{ . | json "" }}` unless `.status_code` is in the 5XX range)
- * `.requestId` → `string` an alias for `.request_id` (hidden from `{{ . | json "" }}`)
-
- Also availabe to the template are the [standard functions
- available to Go `text/template`s][Go `text/template` functions],
- as well as:
-
- * a `json` function that formats arg2 as JSON, using the arg1
- string as the starting indentation. For example, the
- template `{{ json "indent>" "value" }}` would yield the
- string `indent>"value"`.
-
-`"duration"` strings are parsed as a sequence of decimal numbers, each
-with optional fraction and a unit suffix, such as "300ms", "-1.5h" or
-"2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m",
-"h". See [Go `time.ParseDuration`][].
-
-
- If you are using a templating system for your YAML that also makes use of Go templating, then you will need to escape the template strings meant to be interpreted by Edge Stack.
-
-
-[Go `time.ParseDuration`]: https://golang.org/pkg/time/#ParseDuration
-[Go `text/template`]: https://golang.org/pkg/text/template/
-[Go `text/template` functions]: https://golang.org/pkg/text/template/#hdr-Functions
-[`http.Header`]: https://golang.org/pkg/net/http/#Header
-[`jwt.ValidationError`]: https://godoc.org/github.com/dgrijalva/jwt-go#ValidationError
-[Lua Scripts]: ../../../running/ambassador/#lua-scripts
-[Sprig `hasKey`]: https://masterminds.github.io/sprig/dicts.html#haskey
+See the [JWT Filter API reference][] for an overview of all the supported fields.
## JWT path-specific arguments
@@ -275,3 +152,5 @@ spec:
request_handle:headers():remove("x-token-c-optional-unset")
end
```
+
+[JWT Filter API reference]: ../../../../custom-resources/getambassador/v3alpha1/filter-jwt
diff --git a/docs/edge-stack/pre-release/topics/using/filters/oauth2.md b/docs/edge-stack/pre-release/topics/using/filters/oauth2.md
index 51b2ec210..13aa7230c 100644
--- a/docs/edge-stack/pre-release/topics/using/filters/oauth2.md
+++ b/docs/edge-stack/pre-release/topics/using/filters/oauth2.md
@@ -1,6 +1,6 @@
import Alert from '@material-ui/lab/Alert';
-# The OAuth2 Filter
+# Using The OAuth2 Filter
The OAuth2 filter type performs OAuth2 authorization against an identity provider implementing [OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html). The filter is both:
@@ -9,6 +9,10 @@ The OAuth2 filter type performs OAuth2 authorization against an identity provide
This is different from most OAuth implementations where the Authorization Server and the Resource Server are in the same security domain. With Ambassador Edge Stack, the Client and the Resource Server are in the same security domain, and there is an independent Authorization Server.
+
+
+See the [OAuth2 Filter API reference][] for an overview of all the supported fields.
+
## The Ambassador authentication flow
This is what the authentication process looks like at a high level when using Ambassador Edge Stack with an external identity provider. The use case is an end-user accessing a secured app service.
@@ -35,413 +39,6 @@ An identity hub sits between your application and the IdP that authenticates you
The Auth0 docs provide a guide for adding social IdP "[connections](https://auth0.com/docs/identityproviders)" to your Auth0 account, and the Keycloak docs provide a guide for adding social identity "[brokers](https://www.keycloak.org/docs/latest/server_admin/index.html#social-identity-providers)".
-
-## OAuth2 global arguments
-
-```yaml
----
-apiVersion: getambassador.io/v3alpha1
-kind: Filter
-metadata:
- name: "example-oauth2-filter"
- namespace: "example-namespace"
-spec:
- OAuth2:
- authorizationURL: "url" # required
-
- ############################################################################
- # OAuth Client settings #
- ############################################################################
-
- expirationSafetyMargin: "duration" # optional; default is "0"
-
- # Which settings exist depends on the grantType; supported grantTypes
- # are "AuthorizationCode", "Password", and "ClientCredentials".
- grantType: "enum" # optional; default is "AuthorizationCode"
-
- # How should Ambassador authenticate itself to the identity provider?
- clientAuthentication: # optional
- method: "enum" # optional; default is "HeaderPassword"
- jwtAssertion: # optional if method method=="JWTAssertion"; forbidden otherwise
- setClientID: bool # optional; default is false
- # the following members of jwtAssertion only apply when the
- # grantType is NOT "ClientCredentials".
- audience: "string" # optional; default is to use the token endpoint from the authorization URL
- signingMethod: "enum" # optional; default is "RS256"
- lifetime: "duration" # optional; default is "1m"
- setNBF: bool # optional; default is false
- nbfSafetyMargin: "duration" # optional; default is 0s
- setIAT: bool # optional; default is false
- otherClaims: # optional; default is {}
- "string": anything
- otherHeaderParameters: # optional; default is {}
- "string": anything
-
- ## OAuth Client settings: grantType=="AuthorizationCode" ###################
- clientURL: "string" # deprecated; use 'protectedOrigins' instead
- protectedOrigins: # required; must have at least 1 item
- - origin: "url" # required
- internalOrigin: "url" # optional; default is to just use the 'origin' field
- includeSubdomains: bool # optional; default is false
- useSessionCookies: # optional; default is { value: false }
- value: bool # optional; default is true
- ifRequestHeader: # optional; default to apply "useSessionCookies.value" to all requests
- name: "string" # required
- negate: bool # optional; default is false
- # It is invalid to specify both "value" and "valueRegex".
- value: "string" # optional; default is any non-empty string
- valueRegex: "regex" # optional; default is any non-empty string
- clientSessionMaxIdle: "duration" # optional; default is to use the access token lifetime or 14 days if a refresh token is present
- extraAuthorizationParameters: # optional; default is {}
- "string": "string"
- postLogoutRedirectURI: "url" # optional; default is empty string
-
- ## OAuth Client settings: grantType=="AuthorizationCode" or "Password" #####
- clientID: "string" # required
- # The client secret can be specified by including the raw secret as a
- # string in "secret", or by referencing Kubernetes secret with
- # "secretName" (and "secretNamespace"). It is invalid to specify both
- # "secret" and "secretName".
- secret: "string" # required (unless secretName is set)
- secretName: "string" # required (unless secret is set)
- secretNamespace: "string" # optional; default is the same namespace as the Filter
-
- ## OAuth Client settings (grantType=="ClientCredentials") ##################
- #
- # (there are no additional client settings for
- # grantType=="ClientCredentials")
-
- ############################################################################
- # OAuth Resource Server settings #
- ############################################################################
-
- allowMalformedAccessToken: bool # optional; default is false
- accessTokenValidation: "enum" # optional; default is "auto"
- accessTokenJWTFilter: # optional; default is null
- name: "string" # required
- namespace: "string" # optional; default is the same namespace as the Filter
- inheritScopeArgument: bool # optional; default is false
- stripInheritedScope: bool # optional; default is false
- arguments: JWT-Filter-Arguments # optional
- injectRequestHeaders: # optional; default is []
- - name: "header-name-string" # required
- value: "go-template-string" # required
-
- ############################################################################
- # HTTP client settings for talking with the identity provider #
- ############################################################################
-
- insecureTLS: bool # optional; default is false
- renegotiateTLS: "enum" # optional; default is "never"
- maxStale: "duration" # optional; default is "0"
-```
-
-### General settings
-
- - `authorizationURL`: Identifies where to look for the `/.well-known/openid-configuration` descriptor to figure out how to talk to the OAuth2 provider
-
-### OAuth client settings
-
-These settings configure the OAuth Client part of the filter.
-
- - `grantType`: Which type of OAuth 2.0 authorization grant to request from the identity provider. Currently supported are:
- * `"AuthorizationCode"`: Authenticate by redirecting to a login page served by the identity provider.
-
- * `"ClientCredentials"`: Authenticate by requiring that the
- incoming HTTP request include as headers the credentials for
- Ambassador to use to authenticate to the identity provider.
-
- The type of credentials needing to be submitted depends on the
- `clientAuthentication.method` (below):
- + For `"HeaderPassword"` and `"BodyPassword"`, the headers
- `X-Ambassador-Client-ID` and `X-Ambassador-Client-Secret` must
- be set.
- + For `"JWTAssertion"`, the `X-Ambassador-Client-Assertion`
- header must be set to a JWT that is signed by your client
- secret, and conforms with the requirements in RFC 7521 section
- 5.2 and RFC 7523 section 3, as well as any additional specified
- by your identity provider.
-
- * `"Password"`: Authenticate by requiring `X-Ambassador-Username` and `X-Ambassador-Password` on all
- incoming requests, and use them to authenticate with the identity provider using the OAuth2
- `Resource Owner Password Credentials` grant type.
-
- - `expirationSafetyMargin`: Check that access tokens not expire for
- at least this much longer; otherwise consider them to be already
- expired. This provides a safety margin of time for your
- application to send it to an upstream Resource Server that grants
- insufficient leeway to account for clock skew and
- network/application latency.
-
- - `clientAuthentication`: Configures how Ambassador uses the
- `clientID` and `secret` to authenticate itself to the identity
- provider:
- * `method`: Which method Ambassador should use to authenticate
- itself to the identity provider. Currently supported are:
- + `"HeaderPassword"`: Treat the client secret (below) as a
- password, and pack that in to an HTTP header for HTTP Basic
- authentication.
- + `"BodyPassword"`: Treat the client secret (below) as a
- password, and put that in the HTTP request bodies submitted to
- the identity provider. This is NOT RECOMMENDED by RFC 6749,
- and should only be used when using HeaderPassword isn't
- possible.
- + `"JWTAssertion"`: Treat the client secret (below) as a
- password, and put that in the HTTP request bodies submitted to
- the identity provider. This is NOT RECOMMENDED by RFC 6749,
- and should only be used when using HeaderPassword isn't
- possible.
- * `jwtAssertion`: Settings to use when `method: "JWTAssertion"`.
- + `setClientID`: Whether to set the Client ID as an HTTP
- parameter; setting it as an HTTP parameter is optional (per RFC
- 7521 §4.2) because the Client ID is also contained in the JWT
- itself, but some identity providers document that they require
- it to also be set as an HTTP parameter anyway.
- + `audience` (only when `grantType` is not
- `"ClientCredentials"`): The audience value that your identity
- provider requires.
- + `signingMethod` (only when `grantType` is not
- `"ClientCredentials"`): The method to use to sign the JWT; how
- to interpret the `secret` (below). Supported values are:
- - RSA: `"RS256"`, `"RS384"`, `"RS512"`: The secret must be a
- PEM-encoded RSA private key.
- - RSA-PSS: `"PS256"`, `"PS384"`, `"PS512"`: The secret must be
- a PEM-encoded RSA private key.
- - ECDSA: `"ES256"`, `"ES384"`, `"ES512"`: The secret must be a
- PEM-encoded Eliptic Curve private key.
- - HMAC-SHA: `"HS256"`, `"HS384"`, `"HS512"`: The secret is a
- raw string of bytes; it can contain anything.
- + `lifetime` (only when `grantType` is not
- `"ClientCredentials"`): The lifetime of the generated JWT; just
- enough time for the request to the identity provider to
- complete (plus possibly an extra allowance for clock skew).
- + `setNBF` (only when `grantType` is not `"ClientCredentials"`):
- Whether to set the optional "nbf" ("Not Before") claim in the
- generated JWT.
- + `nbfSafetyMargin` (only `setNBF` is true): The safety margin to
- build-in to the "nbf" claim, to allow for clock skew between
- ambassador and the identity provider.
- + `setIAT` (only when `grantType` is not `"ClientCredentials"`):
- Whether to set the optional "iat" ("Issued At") claim in the
- generated JWT.
- + `otherClaims` (only when `grantType` is not
- `"ClientCredentials"`): Any extra non-standard claims to
- include in the generated JWT.
- + `otherHeaderParameters` (only when `grantType` is not
- `"ClientCredentials"`): Any extra JWT header parameters to
- include in the generated JWT non-standard claims to include in
- the generated JWT; only the "typ" and "alg" header parameters
- are set by default.
-
-Depending on which `grantType` is used, different settings exist.
-
-Settings that are only valid when `grantType: "AuthorizationCode"` or `grantType: "Password"`:
-
- - `clientID`: The Client ID you get from your identity provider.
- - The client secret you get from your identity provider can be specified 2 different ways:
- * As a string, in the `secret` field.
- * As a Kubernetes `generic` Secret, named by `secretName`/`secretNamespace`. The Kubernetes secret must of
- the `generic` type, with the value stored under the key`oauth2-client-secret`. If `secretNamespace` is not given, it defaults to the namespace of the Filter resource.
- * **Note**: It is invalid to set both `secret` and `secretName`.
-
-Settings that are only valid when `grantType: "AuthorizationCode"`:
-
- - `protectedOrigins`: (You determine these, and must register them
- with your identity provider) Identifies hostnames that can
- appropriately set cookies for the application. Only the scheme
- (`https://`) and authority (`example.com:1234`) parts are used; the
- path part of the URL is ignored.
-
- You will need to register each origin in `protectedOrigins` as an
- authorized callback endpoint with your identity provider. The URL
- will look like
- `{{ORIGIN}}/.ambassador/oauth2/redirection-endpoint`.
-
-
- If you provide more than one `protectedOrigin`, all share the same
- authentication system, so that logging into one origin logs you
- into all origins; to have multiple domains that have separate
- logins, use separate `Filter`s.
-
- + `internalOrigin`: This sub-field of `protectedOrigins[i]` allows
- you to tell Ambassador that there is another gateway in front of
- Ambassador that rewrites the `Host` header, so that on the
- internal network between that gateway and Ambassador, the origin
- appears to be `internalOrigin` instead of `origin`. As a
- special-case the scheme and/or authority of the `internalOrigin`
- may be `*`, which matches any scheme or any domain respectively.
- The `*` is most useful in configurations with exactly one
- protected origin; in such a configuration, Ambassador doesn't
- need to know what the origin looks like on the internal network,
- just that a gateway in front of Ambassador is rewriting it. It
- is invalid to use `*` with `includeSubdomains: true`.
-
- For example, if you have a gateway in front of Ambassador
- handling traffic for `myservice.example.com`, terminating TLS
- and routing that traffic to Ambassador with the name
- `ambassador.internal`, you might write:
-
- ```yaml
- protectedOrigins:
- - origin: https://myservice.example.com
- internalOrigin: http://ambassador.internal
- ```
-
- or, to avoid being fragile to renaming `ambassador.internal` to
- something else, since there are not multiple origins that the
- Filter must distinguish between, you could instead write:
-
- ```yaml
- protectedOrigins:
- - origin: https://myservice.example.com
- internalOrigin: "*://*"
- ```
-
- - `clientURL` is deprecated, and is equivalent to setting
-
- ```yaml
- protectedOrigins:
- - origin: clientURL-value
- internalOrigin: "*://*"
- ```
-
-- `postLogoutRedirectURI`: Set this field to a valid URL to have $productName$ redirect there upon a successful logout. You must register the following endpoint with your IDP as the Post Logout Redirect `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect`. This informs your IDP to redirect back to $productName$ once the IDP has cleared the session data. Once the IDP has redirected back to $productName$, this clears the local $productName$ session information before redirecting to the destination specified by the `postLogoutRedirectURI` value.
- * If Post Logout Redirect is configured in your IDP to `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` then, after a successful logout, a redirect is issued to the URL configured in `postLogoutRedirectURI`.
- * If `{{ORIGIN}}/.ambassador/oauth2/post-logout-redirect` is configured as the Post Logout Redirect in your IDP, but `postLogoutRedirectURI` is not configured in $productName$, then your IDP will error out as it will be expecting specific instructions for the post logout behavior.
- Refer to your IDP’s documentation to verify if it supports Post Logout Redirects.
- For more information on `post_logout_redirect_uri functionality`, refer to the [OpenID Connect RP-Initiated Logout 1.0 specs](https://openid.net/specs/openid-connect-rpinitiated-1_0.html).
-
- - `extraAuthorizationParameters`: Extra (non-standard or extension) OAuth authorization parameters to use. It is not valid to specify a parameter used by OAuth itself ("response_type", "client_id", "redirect_uri", "scope", or "state").
-
- - `clientSessionMaxIdle`: Control how long the session held by Ambassador Edge Stack's OAuth client will last until we automatically expire it.
- * Ambassador Edge Stack creates a new session when submitting requests to the upstream backend server and sets a cookie containing the sessionID. When a user makes a request to a backend service protected by the OAuth2 Filter, the OAuth Client in Ambassador Edge Stack will use the sessionID contained in the cookie to fetch the access token (and optional refresh token) for the current session so that it can be used when submitting a request to the upstream backend service. This session has a limited lifetime before it expires or extended, prompting the user to log back in.
- * Setting a `clientSessionMaxIdle` duration is useful when your IdP is configured to return a refresh token along with an access token from your IdP's authorization server. `clientSessionMaxIdle` can be set to match Ambassador Edge Stack OAuth client's session lifetime to the lifetime of the refresh token configured within the IdP.
- * If this is not set, then we tie the OAuth client's session lifetime to the lifetime of the access token received from the IdP's authorization server when no refresh token is also provided. If there is a refresh token, then by default we set it to be 14 days.
-
- - By default, any cookies set by the Ambassador Edge Stack will be
- set to expire when the session expires naturally. The
- `useSessionCookies` setting may be used to cause session cookies to
- be used instead.
-
- * Normally cookies are set to be deleted at a specific time;
- session cookies are deleted whenever the user closes their web
- browser. This may mean that the cookies are deleted sooner than
- normal if the user closes their web browser; conversely, it may
- mean that cookies persist for longer than normal if the use does
- not close their browser.
- * The cookies being deleted sooner may or may not affect
- user-perceived behavior, depending on the behavior of the
- identity provider.
- * Any cookies persisting longer will not affect behavior of the
- system; Ambassador Edge Stack validates whether the session
- is expired when considering the cookie.
-
- If `useSessionCookies` is non-`null`, then:
-
- * By default it will have the cookies for all requests be
- session cookies or not according to the
- `useSessionCookies.value` sub-argument.
-
- * Setting the `useSessionCookies.ifRequestHeader` sub-argument
- tells it to use `useSessionCookies.value` for requests that
- match the condition, and `!useSessionCookies.value` for
- requests don't match.
-
- When determining if a request matches, it looks at the HTTP
- header field named by `useSessionCookies.ifRequestHeader.name`
- (case-insensitive), and checks if it is either set to (if
- `useSessionCookies.ifRequestHeader.negate: false`) or not set
- to (if `useSessionCookies.ifRequestHeader.negate: true`)...
- + a non-empty string (if neither
- `useSessionCookies.ifRequestHeader.value` nor
- `useSessionCookies.ifRequestHeader.valueRegex` are set)
- + the exact string `value` (case-sensitive) (if
- `useSessionCookies.ifRequestHeader.value` is set)
- + a string that matches the regular expression
- `useSessionCookies.ifRequestHeader.valueRegex` (if
- `valueRegex` is set). This uses [RE2][] syntax (always, not
- obeying [`regex_type` in the `ambassador Module`][]) but does
- not support the `\C` escape sequence.
- + (it is invalid to have both `value` and `valueRegex` set)
-
-### OAuth resource server settings
-
- - `allowMalformedAccessToken`: Allow any access token, even if they are not RFC 6750-compliant.
- - `injectRequestHeaders` injects HTTP header fields in to the request before sending it to the upstream service; where the header value can be set based on the JWT value.
- If an OAuth2 filter is chained with a JWT filter with `injectRequestHeaders` configured, both sets of headers will be injected.
- If the same header is injected in both filters, the OAuth2 filter will populate the value.
- The value is specified as a [Go `text/template`][] string, with the following data made available to it:
-
- * `.token.Raw` → `string` the access token raw JWT
- * `.token.Header` → `map[string]interface{}` the access token JWT header, as parsed JSON
- * `.token.Claims` → `map[string]interface{}` the access token JWT claims, as parsed JSON
- * `.token.Signature` → `string` the access token signature
- * `.idToken.Raw` → `string` the raw id token JWT
- * `.idToken.Header` → `map[string]interface{}` the id token JWT header, as parsed JSON
- * `.idToken.Claims` → `map[string]interface{}` the id token JWT claims, as parsed JSON
- * `.idToken.Signature` → `string` the id token signature
- * `.httpRequestHeader` → [`http.Header`][] a copy of the header of the incoming HTTP request. Any changes to `.httpRequestHeader` (such as by using using `.httpRequestHeader.Set`) have no effect. It is recommended to use `.httpRequestHeader.Get` instead of treating it as a map, in order to handle capitalization correctly.
-
- - `accessTokenValidation`: How to verify the liveness and scope of Access Tokens issued by the identity provider. Valid values are either `"auto"`, `"jwt"`, or `"userinfo"`. Empty or unset is equivalent to `"auto"`.
- * `"jwt"`: Validates the Access Token as a JWT.
- + By default: It accepts the RS256, RS384, or RS512 signature
- algorithms, and validates the signature against the JWKS from
- OIDC Discovery. It then validates the `exp`, `iat`, `nbf`,
- `iss` (with the Issuer from OIDC Discovery), and `scope`
- claims: if present, none of the scope values are required to be
- present. This relies on the identity provider using
- non-encrypted signed JWTs as Access Tokens, and configuring the
- signing appropriately
- + This behavior can be modified by delegating to [`JWT`
- Filter](../jwt/) with `accessTokenJWTFilter`:
- - `name` and `namespace` are used to identify which JWT Filter
- to use. It is an error to point at a Filter that is not a
- JWT filter.
- - `arguments` is is the same as the `arguments` field when
- referring to a JWT Filter from a FilterPolicy.
- - `inheritScopeArgument` sets whether to inherit the `scope`
- argument from the FilterPolicy rule that triggered the OAuth2
- Filter (similarly special-casing the `offline_access` scope
- value); if the `arguments` field also specifies a `scope`
- argument, then the union of the two is used.
- - `stripInheritedScope` modifies the behavior of
- `inheritScopeArgument`. Some identity providers use scope
- values that are URIs when speaking OAuth, but when encoding
- those scope values in to a JWT the provider strips the
- leading path of the value; removing everything up to and
- including the last "/" in the value. Setting
- `stripInheritedScope` mimics this when passing the required
- scope to the JWT Filter. It is meaningless to set
- `stripInheritedScope` if `inheritScopeArgument` is not set.
- * `"userinfo"`: Validates the access token by polling the OIDC UserInfo Endpoint. This means that the Ambassador Edge Stack must initiate an HTTP request to the identity provider for each authorized request to a protected resource. This performs poorly, but functions properly with a wider range of identity providers. It is not valid to set `accessTokenJWTFilter` if `accessTokenValidation: userinfo`.
- * `"auto"` attempts to do `"jwt"` validation if any of these
- conditions are true:
-
- + `accessTokenJWTFilter` is set, or
- + `grantType` is `"ClientCredentials"`, or
- + the Access Token parses as a JWT and the signature is valid,
-
- and otherwise falls back to `"userinfo"` validation.
-
-[RE2]: https://github.com/google/re2/wiki/Syntax
-[`regex_type` in the `ambassador Module`]: ../../../running/ambassador/
-
-### HTTP client
-
-These HTTP client settings are used for talking to the identity
-provider:
-
- - `maxStale`: How long to keep stale cached OIDC replies for. This sets the `max-stale` Cache-Control directive on requests, and also ignores the `no-store` and `no-cache` Cache-Control directives on responses. This is useful for maintaining good performance when working with identity providers with misconfigured Cache-Control. Note that if you are reusing the same `authorizationURL` and `jwksURI` across different OAuth and JWT filters respectively, then you MUST set `maxStale` as a consistent value on each filter to get predictable caching behavior.
- - `insecureTLS` disables TLS verification when speaking to an identity provider with an `https://` `authorizationURL`. This is discouraged in favor of either using plain `http://` or [installing a self-signed certificate](../#installing-self-signed-certificates).
- - `renegotiateTLS` allows a remote server to request TLS renegotiation. Accepted values are "never", "onceAsClient", and "freelyAsClient".
-
-`"duration"` strings are parsed as a sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". See [Go `time.ParseDuration`](https://golang.org/pkg/time/#ParseDuration).
-
## OAuth2 path-specific arguments
```yaml
@@ -518,10 +115,10 @@ spec:
+ the exact string `value` (case-sensitive) (if `value` is set)
+ a string that matches the regular expression `valueRegex` (if
`valueRegex` is set). This uses [RE2][] syntax (always, not
- obeying [`regex_type` in the `ambassador Module`][]) but does
+ obeying `regex_type` in the `Module`) but does
not support the `\C` escape sequence.
* By default, it serves an authorization-denied error page; by default HTTP 403 ("Forbidden"), but this can be configured by the `httpStatusCode` sub-argument.
- * __DEPRECATED__ Instead of serving that simple error page, it can instead be configured to call out to a list of other Filters, by setting the `filters` list. The syntax and semantics of this list are the same as `.spec.rules[].filters` in a [`FilterPolicy`](../#filterpolicy-definition). Be aware that if one of these filters modify the request rather than returning a response, then the request will be allowed through to the backend service, even though the `OAuth2` Filter denied it.
+ * __DEPRECATED__ Instead of serving that simple error page, it can instead be configured to call out to a list of other Filters, by setting the `filters` list. The syntax and semantics of this list are the same as `.spec.rules[].filters` in a `FilterPolicy`. Be aware that if one of these filters modify the request rather than returning a response, then the request will be allowed through to the backend service, even though the `OAuth2` Filter denied it.
* It is invalid to specify both `httpStatusCode` and `filters`.
## XSRF protection
@@ -535,7 +132,7 @@ Applications using request submission formats other than HTML forms should perfo
- Prior versions of the Ambassador Edge Stack did not have an ambassador_xsrf.NAME.NAMESPACE cookie, and instead required you to use the ambassador_session.NAME.NAMESPACE cookie. The ambassador_session.NAME.NAMESPACE cookie should no longer be used for XSRF-protection purposes.
+ Prior versions of Ambassador Edge Stack did not have an ambassador_xsrf.NAME.NAMESPACE cookie, and instead required you to use the ambassador_session.NAME.NAMESPACE cookie. The ambassador_session.NAME.NAMESPACE cookie should no longer be used for XSRF-protection purposes.
## RP-initiated logout
@@ -575,7 +172,6 @@ form-encoded values that you need to include:
```
-
```html