From d33705afa2a0fbbf128e5f06a125cb71ba4885dc Mon Sep 17 00:00:00 2001 From: Steve Kriss Date: Fri, 28 Jan 2022 08:39:25 -0700 Subject: [PATCH] v1.20.0 site updates (#4302) * Prepare documentation site for v1.20.0 release. Signed-off-by: Steve Kriss * Add changelog for v1.20.0 release. Signed-off-by: Steve Kriss * upgrade instructions and compatibility info Signed-off-by: Steve Kriss --- ...v1.20.0-beta.1.md => CHANGELOG-v1.20.0.md} | 166 +- changelogs/unreleased/3707-tsaarni-minor.md | 5 - .../unreleased/4020-sunjayBhatia-docs.md | 1 - changelogs/unreleased/4027-skriss-small.md | 1 - changelogs/unreleased/4047-skriss-major.md | 9 - changelogs/unreleased/4065-tsaarni-major.md | 9 - changelogs/unreleased/4099-skriss-minor.md | 4 - .../unreleased/4110-sunjayBhatia-small.md | 1 - changelogs/unreleased/4115-skriss-docs.md | 1 - .../unreleased/4117-stevesloka-minor.md | 8 - changelogs/unreleased/4119-skriss-minor.md | 7 - changelogs/unreleased/4120-skriss-small.md | 1 - changelogs/unreleased/4123-skriss-small.md | 1 - .../unreleased/4126-stevesloka-minor.md | 4 - .../unreleased/4133-stevesloka-minor.md | 7 - changelogs/unreleased/4138-skriss-minor.md | 6 - .../unreleased/4141-sunjayBhatia-minor.md | 5 - .../unreleased/4142-sunjayBhatia-small.md | 2 - .../unreleased/4145-stevesloka-small.md | 1 - changelogs/unreleased/4148-abhide-small.md | 1 - changelogs/unreleased/4151-tsaarni-small.md | 1 - .../unreleased/4152-sunjayBhatia-small.md | 1 - changelogs/unreleased/4153-skriss-small.md | 1 - changelogs/unreleased/4160-skriss-minor.md | 4 - changelogs/unreleased/4165-youngnick-minor.md | 26 - changelogs/unreleased/4166-skriss-small.md | 1 - changelogs/unreleased/4169-skriss-minor.md | 5 - .../unreleased/4171-sunjayBhatia-minor.md | 8 - .../unreleased/4173-sunjayBhatia-docs.md | 2 - changelogs/unreleased/4186-skriss-minor.md | 4 - .../unreleased/4197-sunjayBhatia-small.md | 2 - changelogs/unreleased/4200-lrewega-small.md | 1 - .../unreleased/4201-stevesloka-minor.md | 7 - .../unreleased/4202-sunjayBhatia-minor.md | 8 - changelogs/unreleased/4209-skriss-small.md | 1 - changelogs/unreleased/4212-skriss-small.md | 1 - changelogs/unreleased/4219-skriss-small.md | 1 - changelogs/unreleased/4232-skriss-small.md | 1 - changelogs/unreleased/4236-skriss-small.md | 1 - .../unreleased/4244-stevesloka-minor.md | 38 - changelogs/unreleased/4261-skriss-small.md | 1 - changelogs/unreleased/4262-skriss-docs.md | 1 - changelogs/unreleased/4268-skriss-small.md | 1 - .../unreleased/4271-pablo-ruth-small.md | 1 - .../unreleased/4273-sunjayBhatia-small.md | 1 - .../unreleased/4279-sunjayBhatia-docs.md | 1 - .../unreleased/4281-sunjayBhatia-small.md | 1 - changelogs/unreleased/4287-skriss-small.md | 1 - changelogs/unreleased/4288-skriss-docs.md | 1 - site/config.yaml | 3 +- site/content/docs/v1.20.0/_index.md | 47 + site/content/docs/v1.20.0/architecture.md | 39 + .../docs/v1.20.0/config/access-logging.md | 126 + .../docs/v1.20.0/config/annotations.md | 93 + .../docs/v1.20.0/config/api-reference.html | 6167 +++++++++++++++++ site/content/docs/v1.20.0/config/api.md | 3 + .../v1.20.0/config/client-authorization.md | 123 + .../docs/v1.20.0/config/cookie-rewriting.md | 109 + site/content/docs/v1.20.0/config/cors.md | 74 + .../config/external-service-routing.md | 47 + .../docs/v1.20.0/config/fundamentals.md | 196 + .../docs/v1.20.0/config/health-checks.md | 83 + .../v1.20.0/config/inclusion-delegation.md | 135 + site/content/docs/v1.20.0/config/ingress.md | 92 + .../docs/v1.20.0/config/rate-limiting.md | 366 + .../docs/v1.20.0/config/request-rewriting.md | 259 + .../docs/v1.20.0/config/request-routing.md | 322 + .../docs/v1.20.0/config/tls-delegation.md | 77 + .../docs/v1.20.0/config/tls-termination.md | 255 + .../docs/v1.20.0/config/upstream-tls.md | 98 + .../docs/v1.20.0/config/virtual-hosts.md | 136 + .../content/docs/v1.20.0/config/websockets.md | 25 + site/content/docs/v1.20.0/configuration.md | 455 ++ site/content/docs/v1.20.0/deploy-options.md | 222 + site/content/docs/v1.20.0/github.md | 80 + site/content/docs/v1.20.0/grpc-tls-howto.md | 169 + .../content/docs/v1.20.0/img/archoverview.png | Bin 0 -> 78807 bytes .../v1.20.0/img/contour_deployment_in_k8s.png | Bin 0 -> 77339 bytes .../docs/v1.20.0/img/shutdownmanager.png | Bin 0 -> 51051 bytes .../v1.20.0/img/source/shutdownmanager.drawio | 1 + site/content/docs/v1.20.0/redeploy-envoy.md | 81 + .../docs/v1.20.0/start-contributing.md | 139 + site/content/docs/v1.20.0/troubleshooting.md | 38 + .../troubleshooting/contour-debug-log.md | 6 + .../v1.20.0/troubleshooting/contour-graph.md | 25 + .../troubleshooting/contour-xds-resources.md | 19 + .../troubleshooting/envoy-admin-interface.md | 32 + .../troubleshooting/envoy-debug-log.md | 8 + .../docs/v1.20.0/troubleshooting/operator.md | 91 + .../troubleshooting/profiling-contour.md | 14 + .../content/resources/compatibility-matrix.md | 22 +- site/content/resources/upgrading.md | 70 +- site/data/docs/toc-mapping.yml | 1 + site/data/docs/v1-20-0-toc.yml | 121 + versions.yaml | 13 +- 95 files changed, 10588 insertions(+), 257 deletions(-) rename changelogs/{CHANGELOG-v1.20.0-beta.1.md => CHANGELOG-v1.20.0.md} (70%) delete mode 100644 changelogs/unreleased/3707-tsaarni-minor.md delete mode 100644 changelogs/unreleased/4020-sunjayBhatia-docs.md delete mode 100644 changelogs/unreleased/4027-skriss-small.md delete mode 100644 changelogs/unreleased/4047-skriss-major.md delete mode 100644 changelogs/unreleased/4065-tsaarni-major.md delete mode 100644 changelogs/unreleased/4099-skriss-minor.md delete mode 100644 changelogs/unreleased/4110-sunjayBhatia-small.md delete mode 100644 changelogs/unreleased/4115-skriss-docs.md delete mode 100644 changelogs/unreleased/4117-stevesloka-minor.md delete mode 100644 changelogs/unreleased/4119-skriss-minor.md delete mode 100644 changelogs/unreleased/4120-skriss-small.md delete mode 100644 changelogs/unreleased/4123-skriss-small.md delete mode 100644 changelogs/unreleased/4126-stevesloka-minor.md delete mode 100644 changelogs/unreleased/4133-stevesloka-minor.md delete mode 100644 changelogs/unreleased/4138-skriss-minor.md delete mode 100644 changelogs/unreleased/4141-sunjayBhatia-minor.md delete mode 100644 changelogs/unreleased/4142-sunjayBhatia-small.md delete mode 100644 changelogs/unreleased/4145-stevesloka-small.md delete mode 100644 changelogs/unreleased/4148-abhide-small.md delete mode 100644 changelogs/unreleased/4151-tsaarni-small.md delete mode 100644 changelogs/unreleased/4152-sunjayBhatia-small.md delete mode 100644 changelogs/unreleased/4153-skriss-small.md delete mode 100644 changelogs/unreleased/4160-skriss-minor.md delete mode 100644 changelogs/unreleased/4165-youngnick-minor.md delete mode 100644 changelogs/unreleased/4166-skriss-small.md delete mode 100644 changelogs/unreleased/4169-skriss-minor.md delete mode 100644 changelogs/unreleased/4171-sunjayBhatia-minor.md delete mode 100644 changelogs/unreleased/4173-sunjayBhatia-docs.md delete mode 100644 changelogs/unreleased/4186-skriss-minor.md delete mode 100644 changelogs/unreleased/4197-sunjayBhatia-small.md delete mode 100644 changelogs/unreleased/4200-lrewega-small.md delete mode 100644 changelogs/unreleased/4201-stevesloka-minor.md delete mode 100644 changelogs/unreleased/4202-sunjayBhatia-minor.md delete mode 100644 changelogs/unreleased/4209-skriss-small.md delete mode 100644 changelogs/unreleased/4212-skriss-small.md delete mode 100644 changelogs/unreleased/4219-skriss-small.md delete mode 100644 changelogs/unreleased/4232-skriss-small.md delete mode 100644 changelogs/unreleased/4236-skriss-small.md delete mode 100644 changelogs/unreleased/4244-stevesloka-minor.md delete mode 100644 changelogs/unreleased/4261-skriss-small.md delete mode 100644 changelogs/unreleased/4262-skriss-docs.md delete mode 100644 changelogs/unreleased/4268-skriss-small.md delete mode 100644 changelogs/unreleased/4271-pablo-ruth-small.md delete mode 100644 changelogs/unreleased/4273-sunjayBhatia-small.md delete mode 100644 changelogs/unreleased/4279-sunjayBhatia-docs.md delete mode 100644 changelogs/unreleased/4281-sunjayBhatia-small.md delete mode 100644 changelogs/unreleased/4287-skriss-small.md delete mode 100644 changelogs/unreleased/4288-skriss-docs.md create mode 100644 site/content/docs/v1.20.0/_index.md create mode 100644 site/content/docs/v1.20.0/architecture.md create mode 100644 site/content/docs/v1.20.0/config/access-logging.md create mode 100644 site/content/docs/v1.20.0/config/annotations.md create mode 100644 site/content/docs/v1.20.0/config/api-reference.html create mode 100644 site/content/docs/v1.20.0/config/api.md create mode 100644 site/content/docs/v1.20.0/config/client-authorization.md create mode 100644 site/content/docs/v1.20.0/config/cookie-rewriting.md create mode 100644 site/content/docs/v1.20.0/config/cors.md create mode 100644 site/content/docs/v1.20.0/config/external-service-routing.md create mode 100644 site/content/docs/v1.20.0/config/fundamentals.md create mode 100644 site/content/docs/v1.20.0/config/health-checks.md create mode 100644 site/content/docs/v1.20.0/config/inclusion-delegation.md create mode 100644 site/content/docs/v1.20.0/config/ingress.md create mode 100644 site/content/docs/v1.20.0/config/rate-limiting.md create mode 100644 site/content/docs/v1.20.0/config/request-rewriting.md create mode 100644 site/content/docs/v1.20.0/config/request-routing.md create mode 100644 site/content/docs/v1.20.0/config/tls-delegation.md create mode 100644 site/content/docs/v1.20.0/config/tls-termination.md create mode 100644 site/content/docs/v1.20.0/config/upstream-tls.md create mode 100644 site/content/docs/v1.20.0/config/virtual-hosts.md create mode 100644 site/content/docs/v1.20.0/config/websockets.md create mode 100644 site/content/docs/v1.20.0/configuration.md create mode 100644 site/content/docs/v1.20.0/deploy-options.md create mode 100644 site/content/docs/v1.20.0/github.md create mode 100644 site/content/docs/v1.20.0/grpc-tls-howto.md create mode 100644 site/content/docs/v1.20.0/img/archoverview.png create mode 100644 site/content/docs/v1.20.0/img/contour_deployment_in_k8s.png create mode 100644 site/content/docs/v1.20.0/img/shutdownmanager.png create mode 100644 site/content/docs/v1.20.0/img/source/shutdownmanager.drawio create mode 100644 site/content/docs/v1.20.0/redeploy-envoy.md create mode 100644 site/content/docs/v1.20.0/start-contributing.md create mode 100644 site/content/docs/v1.20.0/troubleshooting.md create mode 100644 site/content/docs/v1.20.0/troubleshooting/contour-debug-log.md create mode 100644 site/content/docs/v1.20.0/troubleshooting/contour-graph.md create mode 100644 site/content/docs/v1.20.0/troubleshooting/contour-xds-resources.md create mode 100644 site/content/docs/v1.20.0/troubleshooting/envoy-admin-interface.md create mode 100644 site/content/docs/v1.20.0/troubleshooting/envoy-debug-log.md create mode 100644 site/content/docs/v1.20.0/troubleshooting/operator.md create mode 100644 site/content/docs/v1.20.0/troubleshooting/profiling-contour.md create mode 100644 site/data/docs/v1-20-0-toc.yml diff --git a/changelogs/CHANGELOG-v1.20.0-beta.1.md b/changelogs/CHANGELOG-v1.20.0.md similarity index 70% rename from changelogs/CHANGELOG-v1.20.0-beta.1.md rename to changelogs/CHANGELOG-v1.20.0.md index 58aa08fd9cc..b5edbe78bff 100644 --- a/changelogs/CHANGELOG-v1.20.0-beta.1.md +++ b/changelogs/CHANGELOG-v1.20.0.md @@ -1,7 +1,16 @@ -We are delighted to present the first beta for Contour v1.20.0, our layer 7 HTTP reverse proxy for Kubernetes clusters. +We are delighted to present version v1.20.0 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. -**Please note that this is pre-release software**, and as such we do not recommend installing it in production environments. -Feedback and bug reports are welcome! +A big thank you to everyone who contributed to the release. + + +- [Major Changes](#major-changes) +- [Minor Changes](#minor-changes) +- [Other Changes](#other-changes) +- [Docs Changes](#docs-changes) +- [Deprecations/Removals](#deprecation-and-removal-notices) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) +- [Community Thanks!](#community-thanks) # Major Changes @@ -28,6 +37,7 @@ For further information, see [Contour architecture](https://projectcontour.io/do (#4065, @tsaarni) + # Minor Changes ## Metrics over HTTPS @@ -66,27 +76,12 @@ For more information, see the [Gateway API documentation](https://gateway-api.si (#4119, @skriss) -## Gateway API: support ReferencePolicy - -Contour now supports the `ReferencePolicy` CRD in Gateway API v1alpha2. -`ReferencePolicy` enables certain cross-namespace references to be allowed in Gateway API. -The primary use case is to enable routes (e.g. `HTTPRoutes`, `TLSRoutes`) to reference backend `Services` in different namespaces. -When Contour processes a route that references a service in a different namespace, it will check for a `ReferencePolicy` that applies to the route and service, and if one exists, it will allow the reference. - -(#4138, @skriss) - -## Gateway API: set Gateway Listener status fields - -Contour now sets the `.status.listeners.supportedKinds` and `.status.listeners.attachedRoutes` fields on Gateways for Gateway API. -The first describes the list of route groups/kinds that the listener supports, and the second captures the total number of routes that are successfully attached to the listener. - -(#4160, @skriss) - -## Set Gateway listener conditions +## Add Envoy Deployment Example -Contour now sets various Gateway listener conditions as it processes them, including the "Ready", "Detached", and "ResolvedRefs" condition types, to provide more visibility to the user as to whether their listeners are defined correctly or not. +The examples now include a way to deploy Envoy as a Deployment vs a Daemonset. +This can assist in allowing Envoy to drain connections cleanly when the Kubernetes cluster size is scaled down. -(#4186, @skriss) +(#4126, @stevesloka) ## Default status on HTTPProxy resources @@ -98,14 +93,30 @@ object and updates the status. (#4133, @stevesloka) +## Gateway API: support ReferencePolicy + +Contour now supports the `ReferencePolicy` CRD in Gateway API v1alpha2. +`ReferencePolicy` enables certain cross-namespace references to be allowed in Gateway API. +The primary use case is to enable routes (e.g. `HTTPRoutes`, `TLSRoutes`) to reference backend `Services` in different namespaces. +When Contour processes a route that references a service in a different namespace, it will check for a `ReferencePolicy` that applies to the route and service, and if one exists, it will allow the reference. + +(#4138, @skriss) + ## Source IP hash based load balancing Contour users can now configure their load balancing policies on `HTTPProxy` resources to hash the source IP of a client to ensure consistent routing to a backend service instance. Using this feature combined with header value hashing can implement advanced request routing and session affinity. Note that if you are using a load balancer to front your Envoy deployment, you will need to ensure it preserves client source IP addresses to ensure this feature is effective. -See [this page](https://projectcontour.io/docs/main/config/request-routing/#load-balancing-strategy) for more details on this feature. +See [this page](https://projectcontour.io/docs/v1.20.0/config/request-routing/#load-balancing-strategy) for more details on this feature. (#4141, @sunjayBhatia) +## Gateway API: set Gateway Listener status fields + +Contour now sets the `.status.listeners.supportedKinds` and `.status.listeners.attachedRoutes` fields on Gateways for Gateway API. +The first describes the list of route groups/kinds that the listener supports, and the second captures the total number of routes that are successfully attached to the listener. + +(#4160, @skriss) + ## TLS Certificate validation updates Contour now allows non-server certificates that do not have a CN or SAN set, which mostly fixes @@ -146,13 +157,29 @@ Note that if no TCPProxy service weights are defined, traffic continues to be eq `contour serve` leader election configuration via config file has been deprecated. The preferred way to configure leader election parameters is now via command line flags. -See [here](https://projectcontour.io/docs/main/configuration/#serve-flags) for more detail on the new leader election flags. +See [here](https://projectcontour.io/docs/v1.20.0/configuration/#serve-flags) for more detail on the new leader election flags. *Note:* If you are using the v1alpha1 ContourConfiguration CRD, leader election configuration has been removed from that CRD as well. Leader election configuration is not something that will be dynamically configurable once Contour implements configuration reloading via that CRD. (#4171, @sunjayBhatia) +## Set Gateway listener conditions + +Contour now sets various Gateway listener conditions as it processes them, including the "Ready", "Detached", and "ResolvedRefs" condition types, to provide more visibility to the user as to whether their listeners are defined correctly or not. + +(#4186, @skriss) + +## HTTP Request Redirect Policy + +HTTPProxy.Route now has a HTTPRequestRedirectPolicy which allows for routes to specify a RequestRedirectPolicy. +This policy will allow a redirect to be configured for a specific set of Conditions within a single route. +The policy can be configured with a `Hostname`, `StatusCode`, `Scheme`, and `Port`. + +Additionally, Services on a Route are now optional when a request redirect is defined. + +(#4201, @stevesloka) + ## Transition to controller-runtime managed leader election Contour now utilizes [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) Manager based leader election and coordination of subroutines. @@ -164,15 +191,47 @@ This change should be a no-op for most users, however be sure to re-apply the re (#4202, @sunjayBhatia) -## HTTP Request Redirect Policy +## HTTPRedirectPolicy allows for Path rewriting + +Adds a `Path` & `Prefix` field to the `HTTPProxy.Spec.Route.RequestRedirectPolicy` which allows +for redirects to also specify the path or prefix to redirect to. When specified, an +HTTP 302 response will be sent to the requestor with the new path or prefix specified. + +_Note: Only one of path or prefix can be specified on a single route._ + +Sample HTTPProxy: + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: redirect-path +spec: + virtualhost: + fqdn: redirect.projectcontour.io + routes: + - conditions: + - prefix: /blog + services: + - name: blogservice + port: 80 + requestRedirectPolicy: + path: /blog/site +``` -HTTPProxy.Route now has a HTTPRequestRedirectPolicy which allows for routes to specify a RequestRedirectPolicy. -This policy will allow a redirect to be configured for a specific set of Conditions within a single route. -The policy can be configured with a `Hostname`, `StatusCode`, `Scheme`, and `Port`. +Request: +```bash +$ curl -i http://redirect.projectcontour.io/blog -Additionally, Services on a Route are now optional when a request redirect is defined. +HTTP/2 302 +location: http://redirect.projectcontour.io/blog/site +vary: Accept-Encoding +date: Wed, 15 Dec 2021 20:42:04 GMT +server: envoy +``` + +(#4244, @stevesloka) -(#4201, @stevesloka) # Other Changes - Sets conditions of "Accepted: false" and "ValidBackendRefs: false" on `TLSRoutes` when all backend refs have a weight of 0 explicitly set. (#4027, @skriss) @@ -182,6 +241,7 @@ Additionally, Services on a Route are now optional when a request redirect is de - Update to using Envoy bootstrap Admin [`access_log` field](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/accesslog/v3/accesslog.proto#envoy-v3-api-msg-config-accesslog-v3-accesslog) instead of deprecated `access_log_path` (deprecated in Envoy v1.18.0) (#4142, @sunjayBhatia) - Update to using Envoy [XFF Original IP Detection extension](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/original_ip_detection/xff/v3/xff.proto) instead of HTTPConnectionManager `xff_num_trusted_hops` field (deprecated in Envoy v1.19.0) (#4142, @sunjayBhatia) - HTTPProxy resources now support wildcard fqdn's in the form `*.projectcontour.io`. (#4145, @stevesloka) +- Adds a new field, `withRequestBody`, within HTTPProxy's `authorizationServer` to allow client request body to be sent to the external authorization server. See [External Authorization Configuration Overview](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto#envoy-v3-api-msg-extensions-filters-http-ext-authz-v3-extauthz) for more information. (#4148, @abhide) - Timeout for upstream network connection timeout increased from 250 msec to 2 seconds. (#4151, @tsaarni) - Fix accidental negation of disableAllowChunkedLength configuration flag. (#4152, @sunjayBhatia) - Replaces the use of the dynamic Kubernetes client with the controller-runtime client. (#4153, @skriss) @@ -190,37 +250,51 @@ Additionally, Services on a Route are now optional when a request redirect is de - Removes spec.ttlSecondsAfterFinished from certgen job in versioned releases, as immediately deleting it upon completion will not be useful for most consumers. (#4200, @lrewega) - Gateway API: set an HTTPRoute condition of "ValidMatches: false" when a path match does not start with '/' or contains consecutive '/' characters. (#4209, @skriss) - Gateway API: allow Gateways to reference TLS certificates in other namespaces when an applicable ReferencePolicy is defined. See [the Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tls/#cross-namespace-certificate-references) for more information. (#4212, @skriss) +- Gateway API: "core" is no longer allowed as a magic string to reference the core Kubernetes API group. Instead, the empty string should be used to align with the Gateway API spec. (#4219, @skriss) +- Updates Go version to 1.17.5, which includes fixes for CVE-2021-44716 and CVE-2021-44717. See the [Go release announcement](https://groups.google.com/g/golang-announce/c/hcmEScgc00k) for more information. (#4232, @skriss) +- Gateway API: set Gateway's `.status.addresses` based on the IP or hostname of the Envoy service (if it's a LoadBalancer service). (#4236, @skriss) +- Contour has been updated to run on Kubernetes 1.23. It is now tested against Kubernetes 1.21 through 1.23. (#4261, @skriss) +- Update Gateway API to v0.4.1 (#4268, @skriss) +- Adds a new Ingress annotation, `projectcontour.io/tls-cert-namespace`, to allow [TLS Certificate Delegation](https://projectcontour.io/docs/main/config/tls-delegation/) to be used with Ingress v1. (#4271, @pablo-ruth) +- Bump Envoy to v1.21.0. See [release notes](https://www.envoyproxy.io/docs/envoy/v1.21.0/version_history/current). (#4273, @sunjayBhatia) +- Fixes bug in certgen error handling when writing certs to kubernetes. (#4281, @sunjayBhatia) +- Fixes a bug where the global headers policy `ApplyToIngress` field was being ignored, causing Ingresses never to have the global headers policy applied. (#4287, @skriss) # Docs Changes - Pare down docs versions available in site dropdown. (#4020, @sunjayBhatia) - Updates the cert-manager guide to use the latest versions of Contour and cert-manager as well as Ingress v1 resources. (#4115, @skriss) -- Adds a Gateway API v1alpha2 guide. (#4122, @skriss) - The [Contour deprecation policy](https://projectcontour.io/resources/deprecation-policy/) for Alpha APIs has been updated to be explicitly more lenient in regards to behavior changes and field removal. A new API version is not strictly required when making such changes. (#4173, @sunjayBhatia) +- Updates the Gateway API guide to use Gateway API v1alpha2. (#4262, @skriss) +- Adds documentation for header manipulation when using Ingress v1 resources. (#4279, @sunjayBhatia) +- Revise the "Getting Started" guide for clarity and to cover more types of clusters and ways of installing Contour. (#4288, @skriss) -# Installing -The simplest way to install v1.20.0-beta.1 is to apply one of the example configurations: +# Deprecation and Removal Notices -With Gateway API: -```bash -kubectl apply -f https://github.com/projectcontour/contour/blob/v1.20.0-beta.1/examples/render/contour-gateway.yaml -``` +Contour v1.20.0 is the last release that will be pushed to Docker Hub. +We've been pushing images to [GitHub Container Registry](https://github.com/projectcontour/contour/pkgs/container/contour) (GHCR) for several releases now, and all example YAML manifests have been updated to use the GHCR image. +If you haven't yet updated your own install, now is the time to switch to `ghcr.io/projectcontour/contour:v1.20.0`. +Past images will be left in Docker Hub indefinitely. -Without Gateway API: -```bash -kubectl apply -f https://github.com/projectcontour/contour/blob/v1.20.0-beta.1/examples/render/contour.yaml -``` +# Installing and Upgrading -## Compatible Kubernetes Versions +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). -Contour v1.20.0-beta.1 is tested against Kubernetes 1.20 through 1.22 +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). -## Documentation -Documentation corresponding to `v1.20.0-beta.1` can be found at https://projectcontour.io/docs/main/. +# Compatible Kubernetes Versions +Contour v1.20.0 is tested against Kubernetes 1.21 through 1.23. -# Are you a Contour user? We would love to know! +# Community Thanks! +We’re immensely grateful for all the community contributions that help make Contour even better! For this release, special thanks go out to the following contributors: + +- @abhide +- @lrewega +- @pablo-ruth + +# Are you a Contour user? We would love to know! If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://github.com/projectcontour/contour/blob/master/ADOPTERS.md). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/unreleased/3707-tsaarni-minor.md b/changelogs/unreleased/3707-tsaarni-minor.md deleted file mode 100644 index f9f35daa456..00000000000 --- a/changelogs/unreleased/3707-tsaarni-minor.md +++ /dev/null @@ -1,5 +0,0 @@ -### Metrics over HTTPS - -Both Envoy and Contour metrics can now be served over HTTPS. -Server can alternatively also require client to present certificate which is validated against configured CA certificate. -This feature makes it possible to limit the visibility of metrics to authorized clients. diff --git a/changelogs/unreleased/4020-sunjayBhatia-docs.md b/changelogs/unreleased/4020-sunjayBhatia-docs.md deleted file mode 100644 index b7b58774f8a..00000000000 --- a/changelogs/unreleased/4020-sunjayBhatia-docs.md +++ /dev/null @@ -1 +0,0 @@ -Pare down docs versions available in site dropdown. diff --git a/changelogs/unreleased/4027-skriss-small.md b/changelogs/unreleased/4027-skriss-small.md deleted file mode 100644 index 51fbe993aa6..00000000000 --- a/changelogs/unreleased/4027-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Sets conditions of "Accepted: false" and "ValidBackendRefs: false" on `TLSRoutes` when all backend refs have a weight of 0 explicitly set. diff --git a/changelogs/unreleased/4047-skriss-major.md b/changelogs/unreleased/4047-skriss-major.md deleted file mode 100644 index e1373e823c8..00000000000 --- a/changelogs/unreleased/4047-skriss-major.md +++ /dev/null @@ -1,9 +0,0 @@ -## Gateway API v1alpha2 support - -Contour now exclusively supports Gateway API v1alpha2, the latest available version. -This version of Gateway API has a number of breaking changes, which are detailed in [the Gateway API changelog](https://github.com/kubernetes-sigs/gateway-api/blob/master/CHANGELOG.md). -Contour currently supports a single `GatewayClass` and associated `Gateway`, and `HTTPRoutes` and `TLSRoutes` that attach to the `Gateway`. `TCPRoute` and `UDPRoute` are **not** supported. -For a list of other functionality that remains to be implemented, see Contour's [area/gateway-api](https://github.com/projectcontour/contour/labels/area%2Fgateway-api) label. - -As part of this change, support for Gateway API v1alpha1 has been dropped, and any v1alpha1 resources **will not** be automatically converted to v1alpha2 resources because the API has moved to a different API group (from `networking.x-k8s.io` to `gateway.networking.k8s.io`). - diff --git a/changelogs/unreleased/4065-tsaarni-major.md b/changelogs/unreleased/4065-tsaarni-major.md deleted file mode 100644 index 241fb52bab7..00000000000 --- a/changelogs/unreleased/4065-tsaarni-major.md +++ /dev/null @@ -1,9 +0,0 @@ -## xDS management connection between Contour and Envoy set to TLSv1.3 - -The minimum accepted TLS version for Contour xDS server is changed from TLSv1.2 to TLSv1.3. -Previously in Contour 1.19, the maximum accepted TLS version for Envoy xDS client was increased to TLSv1.3 which allows it to connect to Contour xDS server using TLSv1.3. - -If upgrading from a version **prior to Contour 1.19**, the old Envoys will be unable to connect to new Contour until also Envoys are upgraded. -Until that, old Envoys are unable to receive new configuration data. - -For further information, see [Contour architecture](https://projectcontour.io/docs/main/architecture/) and [xDS API](https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol) in Envoy documentation. diff --git a/changelogs/unreleased/4099-skriss-minor.md b/changelogs/unreleased/4099-skriss-minor.md deleted file mode 100644 index 453c00dcc0f..00000000000 --- a/changelogs/unreleased/4099-skriss-minor.md +++ /dev/null @@ -1,4 +0,0 @@ -### Performance improvement for processing configuration - -The performance of Contour's configuration processing has been made more efficient, particularly for clusters with large numbers (i.e. >1k) of HTTPProxies and/or Ingresses. -This means that there should be less of a delay between creating/updating an HTTPProxy/Ingress in Kubernetes, and having it reflected in Envoy's configuration. diff --git a/changelogs/unreleased/4110-sunjayBhatia-small.md b/changelogs/unreleased/4110-sunjayBhatia-small.md deleted file mode 100644 index c580013aa92..00000000000 --- a/changelogs/unreleased/4110-sunjayBhatia-small.md +++ /dev/null @@ -1 +0,0 @@ -Fix panic in Contour startup when using `--root-namespaces` flag diff --git a/changelogs/unreleased/4115-skriss-docs.md b/changelogs/unreleased/4115-skriss-docs.md deleted file mode 100644 index bb347ea918c..00000000000 --- a/changelogs/unreleased/4115-skriss-docs.md +++ /dev/null @@ -1 +0,0 @@ -Updates the cert-manager guide to use the latest versions of Contour and cert-manager as well as Ingress v1 resources. diff --git a/changelogs/unreleased/4117-stevesloka-minor.md b/changelogs/unreleased/4117-stevesloka-minor.md deleted file mode 100644 index c6c98208bd0..00000000000 --- a/changelogs/unreleased/4117-stevesloka-minor.md +++ /dev/null @@ -1,8 +0,0 @@ -### Allow retry policy, num retries to be zero - -The field, NumRetries (e.g. count), in the RetryPolicy allows for a zero to be -specified, however Contour's internal logic would see that as "undefined" -and set it back to the Envoy default of 1. This would never allow the value of -zero to be set. Users can set the value to be -1 which will represent disabling -the retry count. If not specified or set to zero, then the Envoy default value -of 1 is used. \ No newline at end of file diff --git a/changelogs/unreleased/4119-skriss-minor.md b/changelogs/unreleased/4119-skriss-minor.md deleted file mode 100644 index 2495949a9bd..00000000000 --- a/changelogs/unreleased/4119-skriss-minor.md +++ /dev/null @@ -1,7 +0,0 @@ -### Gateway API: implement PathPrefix matching - -Contour now implements Gateway API v1alpha2's "path prefix" matching for `HTTPRoutes`. -This is now the only native form of prefix matching supported by Gateway API, and is a change from v1alpha1. -Path prefix matching means that the prefix specified in an `HTTPRoute` rule must match entire segments of a request's path in order to match it, rather than just be a string prefix. -For example, the prefix `/foo` would match a request for the path `/foo/bar` but not `/foobar`. -For more information, see the [Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.PathMatchType). diff --git a/changelogs/unreleased/4120-skriss-small.md b/changelogs/unreleased/4120-skriss-small.md deleted file mode 100644 index 8fcd4ebe8de..00000000000 --- a/changelogs/unreleased/4120-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Gateway API: adds support for HTTP method matching in `HTTPRoute` rules. See the [Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRouteMatch) for more information. diff --git a/changelogs/unreleased/4123-skriss-small.md b/changelogs/unreleased/4123-skriss-small.md deleted file mode 100644 index eb27f8266d8..00000000000 --- a/changelogs/unreleased/4123-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Gateway API: adds support for the "RequestRedirect" HTTPRoute filter type at the rule level. diff --git a/changelogs/unreleased/4126-stevesloka-minor.md b/changelogs/unreleased/4126-stevesloka-minor.md deleted file mode 100644 index 120bb2d74e0..00000000000 --- a/changelogs/unreleased/4126-stevesloka-minor.md +++ /dev/null @@ -1,4 +0,0 @@ -### Add Envoy Deployment Example - -The examples now include a way to deploy Envoy as a Deployment vs a Daemonset. -This can assist in allowing Envoy to drain connections cleanly when the Kubernetes cluster size is scaled down. \ No newline at end of file diff --git a/changelogs/unreleased/4133-stevesloka-minor.md b/changelogs/unreleased/4133-stevesloka-minor.md deleted file mode 100644 index d19078ac99a..00000000000 --- a/changelogs/unreleased/4133-stevesloka-minor.md +++ /dev/null @@ -1,7 +0,0 @@ -### Default status on HTTPProxy resources - -When a new HTTPProxy is created, if Contour isn't yet running or -functioning properly, then no status is set on the resource. -Defaults of "NotReconciled/Waiting for controller" are now applied -to any new object until an instance of Contour accepts the -object and updates the status. diff --git a/changelogs/unreleased/4138-skriss-minor.md b/changelogs/unreleased/4138-skriss-minor.md deleted file mode 100644 index bf31ef2bf41..00000000000 --- a/changelogs/unreleased/4138-skriss-minor.md +++ /dev/null @@ -1,6 +0,0 @@ -### Gateway API: support ReferencePolicy - -Contour now supports the `ReferencePolicy` CRD in Gateway API v1alpha2. -`ReferencePolicy` enables certain cross-namespace references to be allowed in Gateway API. -The primary use case is to enable routes (e.g. `HTTPRoutes`, `TLSRoutes`) to reference backend `Services` in different namespaces. -When Contour processes a route that references a service in a different namespace, it will check for a `ReferencePolicy` that applies to the route and service, and if one exists, it will allow the reference. diff --git a/changelogs/unreleased/4141-sunjayBhatia-minor.md b/changelogs/unreleased/4141-sunjayBhatia-minor.md deleted file mode 100644 index 30e5093f4ff..00000000000 --- a/changelogs/unreleased/4141-sunjayBhatia-minor.md +++ /dev/null @@ -1,5 +0,0 @@ -### Source IP hash based load balancing - -Contour users can now configure their load balancing policies on `HTTPProxy` resources to hash the source IP of a client to ensure consistent routing to a backend service instance. Using this feature combined with header value hashing can implement advanced request routing and session affinity. Note that if you are using a load balancer to front your Envoy deployment, you will need to ensure it preserves client source IP addresses to ensure this feature is effective. - -See [this page](https://projectcontour.io/docs/v1.20.0/config/request-routing/#load-balancing-strategy) for more details on this feature. \ No newline at end of file diff --git a/changelogs/unreleased/4142-sunjayBhatia-small.md b/changelogs/unreleased/4142-sunjayBhatia-small.md deleted file mode 100644 index fb85298cfee..00000000000 --- a/changelogs/unreleased/4142-sunjayBhatia-small.md +++ /dev/null @@ -1,2 +0,0 @@ -Update to using Envoy bootstrap Admin [`access_log` field](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/accesslog/v3/accesslog.proto#envoy-v3-api-msg-config-accesslog-v3-accesslog) instead of deprecated `access_log_path` (deprecated in Envoy v1.18.0) -Update to using Envoy [XFF Original IP Detection extension](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/original_ip_detection/xff/v3/xff.proto) instead of HTTPConnectionManager `xff_num_trusted_hops` field (deprecated in Envoy v1.19.0) \ No newline at end of file diff --git a/changelogs/unreleased/4145-stevesloka-small.md b/changelogs/unreleased/4145-stevesloka-small.md deleted file mode 100644 index fccf658bdf0..00000000000 --- a/changelogs/unreleased/4145-stevesloka-small.md +++ /dev/null @@ -1 +0,0 @@ -HTTPProxy resources now support wildcard fqdn's in the form `*.projectcontour.io`. \ No newline at end of file diff --git a/changelogs/unreleased/4148-abhide-small.md b/changelogs/unreleased/4148-abhide-small.md deleted file mode 100644 index a8e6fbf9a26..00000000000 --- a/changelogs/unreleased/4148-abhide-small.md +++ /dev/null @@ -1 +0,0 @@ -Adds a new field, `withRequestBody`, within HTTPProxy's `authorizationServer` to allow client request body to be sent to the external authorization server. See [External Authorization Configuration Overview](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto#envoy-v3-api-msg-extensions-filters-http-ext-authz-v3-extauthz) for more information. \ No newline at end of file diff --git a/changelogs/unreleased/4151-tsaarni-small.md b/changelogs/unreleased/4151-tsaarni-small.md deleted file mode 100644 index 044fb642b9b..00000000000 --- a/changelogs/unreleased/4151-tsaarni-small.md +++ /dev/null @@ -1 +0,0 @@ -Timeout for upstream network connection timeout increased from 250 msec to 2 seconds. diff --git a/changelogs/unreleased/4152-sunjayBhatia-small.md b/changelogs/unreleased/4152-sunjayBhatia-small.md deleted file mode 100644 index 77e4771af8d..00000000000 --- a/changelogs/unreleased/4152-sunjayBhatia-small.md +++ /dev/null @@ -1 +0,0 @@ -Fix accidental negation of disableAllowChunkedLength configuration flag. diff --git a/changelogs/unreleased/4153-skriss-small.md b/changelogs/unreleased/4153-skriss-small.md deleted file mode 100644 index 80ec17d80ff..00000000000 --- a/changelogs/unreleased/4153-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Replaces the use of the dynamic Kubernetes client with the controller-runtime client. diff --git a/changelogs/unreleased/4160-skriss-minor.md b/changelogs/unreleased/4160-skriss-minor.md deleted file mode 100644 index 07e533144b4..00000000000 --- a/changelogs/unreleased/4160-skriss-minor.md +++ /dev/null @@ -1,4 +0,0 @@ -### Gateway API: set Gateway Listener status fields - -Contour now sets the `.status.listeners.supportedKinds` and `.status.listeners.attachedRoutes` fields on Gateways for Gateway API. -The first describes the list of route groups/kinds that the listener supports, and the second captures the total number of routes that are successfully attached to the listener. diff --git a/changelogs/unreleased/4165-youngnick-minor.md b/changelogs/unreleased/4165-youngnick-minor.md deleted file mode 100644 index f1d179e1292..00000000000 --- a/changelogs/unreleased/4165-youngnick-minor.md +++ /dev/null @@ -1,26 +0,0 @@ -## TLS Certificate validation updates - -Contour now allows non-server certificates that do not have a CN or SAN set, which mostly fixes -[#2372](https://github.com/projectcontour/contour/issues/2372) and [#3889](https://github.com/projectcontour/contour/issues/3889). - -TLS documentation has been updated to make the rules for Secrets holding TLS information clearer. - -Those rules are: - -For certificates that identify a server, they must: -- be `kubernetes.io/tls` type -- contain `tls.crt`, and `tls.key` keys with the server certificate and key respectively. -- have the first certificate in the `tls.crt` bundle have a CN or SAN field set. - -They may: -- have the `tls.crt` key contain a certificate chain, as long as the first certificate in the chain is the server certificate. -- add a `ca.crt` key that contains a Certificate Authority (CA) certificate or certificates. - -Certificates in the certificate chain that are not server certificates do not need to have a CN or SAN. - -For CA secrets, they must: -- be `Opaque` type -- contain only a `ca.crt` key, not `tls.crt` or `tls.key` - -The `ca.crt` key may contain one or more CA certificates, that do not need to have a CN or SAN. - diff --git a/changelogs/unreleased/4166-skriss-small.md b/changelogs/unreleased/4166-skriss-small.md deleted file mode 100644 index 819f2e555b3..00000000000 --- a/changelogs/unreleased/4166-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Gateway API: Contour no longer sets up RBAC for TCPRoutes and UDPRoutes or watches them, since they are not supported. diff --git a/changelogs/unreleased/4169-skriss-minor.md b/changelogs/unreleased/4169-skriss-minor.md deleted file mode 100644 index b3561bdc981..00000000000 --- a/changelogs/unreleased/4169-skriss-minor.md +++ /dev/null @@ -1,5 +0,0 @@ -### HTTPProxy TCPProxy service weights are now applied - -Previously, Contour did not apply any service weights defined in an HTTPProxy's TCPProxy, and all services were equally weighted. -Now, if those weights are defined, they are applied so traffic is weighted appropriately across the services. -Note that if no TCPProxy service weights are defined, traffic continues to be equally spread across all services. diff --git a/changelogs/unreleased/4171-sunjayBhatia-minor.md b/changelogs/unreleased/4171-sunjayBhatia-minor.md deleted file mode 100644 index 3aa986b28f8..00000000000 --- a/changelogs/unreleased/4171-sunjayBhatia-minor.md +++ /dev/null @@ -1,8 +0,0 @@ -### Leader Election Configuration - -`contour serve` leader election configuration via config file has been deprecated. -The preferred way to configure leader election parameters is now via command line flags. -See [here](https://projectcontour.io/docs/v1.20.0/configuration/#serve-flags) for more detail on the new leader election flags. - -*Note:* If you are using the v1alpha1 ContourConfiguration CRD, leader election configuration has been removed from that CRD as well. -Leader election configuration is not something that will be dynamically configurable once Contour implements configuration reloading via that CRD. diff --git a/changelogs/unreleased/4173-sunjayBhatia-docs.md b/changelogs/unreleased/4173-sunjayBhatia-docs.md deleted file mode 100644 index 6bf908f4126..00000000000 --- a/changelogs/unreleased/4173-sunjayBhatia-docs.md +++ /dev/null @@ -1,2 +0,0 @@ -The [Contour deprecation policy](https://projectcontour.io/resources/deprecation-policy/) for Alpha APIs has been updated to be explicitly more lenient in regards to behavior changes and field removal. -A new API version is not strictly required when making such changes. diff --git a/changelogs/unreleased/4186-skriss-minor.md b/changelogs/unreleased/4186-skriss-minor.md deleted file mode 100644 index 2fb95cdd433..00000000000 --- a/changelogs/unreleased/4186-skriss-minor.md +++ /dev/null @@ -1,4 +0,0 @@ -### Set Gateway listener conditions - -Contour now sets various Gateway listener conditions as it processes them, including the "Ready", "Detached", and "ResolvedRefs" condition types, to provide more visibility to the user as to whether their listeners are defined correctly or not. - diff --git a/changelogs/unreleased/4197-sunjayBhatia-small.md b/changelogs/unreleased/4197-sunjayBhatia-small.md deleted file mode 100644 index 9a789709703..00000000000 --- a/changelogs/unreleased/4197-sunjayBhatia-small.md +++ /dev/null @@ -1,2 +0,0 @@ -Very long (~100 characters) Ingress path prefix matches should now no longer be rejected by Envoy. -See [this issue](https://github.com/projectcontour/contour/issues/4191) for context. diff --git a/changelogs/unreleased/4200-lrewega-small.md b/changelogs/unreleased/4200-lrewega-small.md deleted file mode 100644 index e1537fa110f..00000000000 --- a/changelogs/unreleased/4200-lrewega-small.md +++ /dev/null @@ -1 +0,0 @@ -Removes spec.ttlSecondsAfterFinished from certgen job in versioned releases, as immediately deleting it upon completion will not be useful for most consumers. diff --git a/changelogs/unreleased/4201-stevesloka-minor.md b/changelogs/unreleased/4201-stevesloka-minor.md deleted file mode 100644 index bc767767f81..00000000000 --- a/changelogs/unreleased/4201-stevesloka-minor.md +++ /dev/null @@ -1,7 +0,0 @@ -### HTTP Request Redirect Policy - -HTTPProxy.Route now has a HTTPRequestRedirectPolicy which allows for routes to specify a RequestRedirectPolicy. -This policy will allow a redirect to be configured for a specific set of Conditions within a single route. -The policy can be configured with a `Hostname`, `StatusCode`, `Scheme`, and `Port`. - -Additionally, Services on a Route are now optional when a request redirect is defined. diff --git a/changelogs/unreleased/4202-sunjayBhatia-minor.md b/changelogs/unreleased/4202-sunjayBhatia-minor.md deleted file mode 100644 index f5afef0a1cc..00000000000 --- a/changelogs/unreleased/4202-sunjayBhatia-minor.md +++ /dev/null @@ -1,8 +0,0 @@ -### Transition to controller-runtime managed leader election - -Contour now utilizes [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) Manager based leader election and coordination of subroutines. -With this change, Contour is also transitioning away from using a ConfigMap for leader election. -In this release, Contour now uses a combination of ConfigMap and Lease object. -A future release will remove usage of the ConfigMap resource for leader election. - -This change should be a no-op for most users, however be sure to re-apply the relevant parts of your deployment for RBAC to ensure Contour has access to Lease and Event objects (this would be the ClusterRole in the provided example YAML). diff --git a/changelogs/unreleased/4209-skriss-small.md b/changelogs/unreleased/4209-skriss-small.md deleted file mode 100644 index 6f62314ff48..00000000000 --- a/changelogs/unreleased/4209-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Gateway API: set an HTTPRoute condition of "ValidMatches: false" when a path match does not start with '/' or contains consecutive '/' characters. diff --git a/changelogs/unreleased/4212-skriss-small.md b/changelogs/unreleased/4212-skriss-small.md deleted file mode 100644 index 92a629bb9d6..00000000000 --- a/changelogs/unreleased/4212-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Gateway API: allow Gateways to reference TLS certificates in other namespaces when an applicable ReferencePolicy is defined. See [the Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tls/#cross-namespace-certificate-references) for more information. diff --git a/changelogs/unreleased/4219-skriss-small.md b/changelogs/unreleased/4219-skriss-small.md deleted file mode 100644 index df2c7578e2c..00000000000 --- a/changelogs/unreleased/4219-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Gateway API: "core" is no longer allowed as a magic string to reference the core Kubernetes API group. Instead, the empty string should be used to align with the Gateway API spec. diff --git a/changelogs/unreleased/4232-skriss-small.md b/changelogs/unreleased/4232-skriss-small.md deleted file mode 100644 index 8bbb3430b18..00000000000 --- a/changelogs/unreleased/4232-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Updates Go version to 1.17.5, which includes fixes for CVE-2021-44716 and CVE-2021-44717. See the [Go release announcement](https://groups.google.com/g/golang-announce/c/hcmEScgc00k) for more information. diff --git a/changelogs/unreleased/4236-skriss-small.md b/changelogs/unreleased/4236-skriss-small.md deleted file mode 100644 index 01182c6d662..00000000000 --- a/changelogs/unreleased/4236-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Gateway API: set Gateway's `.status.addresses` based on the IP or hostname of the Envoy service (if it's a LoadBalancer service). diff --git a/changelogs/unreleased/4244-stevesloka-minor.md b/changelogs/unreleased/4244-stevesloka-minor.md deleted file mode 100644 index 7197f518428..00000000000 --- a/changelogs/unreleased/4244-stevesloka-minor.md +++ /dev/null @@ -1,38 +0,0 @@ -## HTTPRedirectPolicy allows for Path rewriting - -Adds a `Path` & `Prefix` field to the `HTTPProxy.Spec.Route.RequestRedirectPolicy` which allows -for redirects to also specify the path or prefix to redirect to. When specified, an -HTTP 302 response will be sent to the requestor with the new path or prefix specified. - -_Note: Only one of path or prefix can be specified on a single route._ - -Sample HTTPProxy: - -```yaml -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: redirect-path -spec: - virtualhost: - fqdn: redirect.projectcontour.io - routes: - - conditions: - - prefix: /blog - services: - - name: blogservice - port: 80 - requestRedirectPolicy: - path: /blog/site -``` - -Request: -```bash -$ curl -i http://redirect.projectcontour.io/blog - -HTTP/2 302 -location: http://redirect.projectcontour.io/blog/site -vary: Accept-Encoding -date: Wed, 15 Dec 2021 20:42:04 GMT -server: envoy -``` \ No newline at end of file diff --git a/changelogs/unreleased/4261-skriss-small.md b/changelogs/unreleased/4261-skriss-small.md deleted file mode 100644 index 12a1590b76f..00000000000 --- a/changelogs/unreleased/4261-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Contour has been updated to run on Kubernetes 1.23. It is now tested against Kubernetes 1.21 through 1.23. diff --git a/changelogs/unreleased/4262-skriss-docs.md b/changelogs/unreleased/4262-skriss-docs.md deleted file mode 100644 index 9a646484db2..00000000000 --- a/changelogs/unreleased/4262-skriss-docs.md +++ /dev/null @@ -1 +0,0 @@ -Updates the Gateway API guide to use Gateway API v1alpha2. diff --git a/changelogs/unreleased/4268-skriss-small.md b/changelogs/unreleased/4268-skriss-small.md deleted file mode 100644 index dc5109e2980..00000000000 --- a/changelogs/unreleased/4268-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Update Gateway API to v0.4.1 diff --git a/changelogs/unreleased/4271-pablo-ruth-small.md b/changelogs/unreleased/4271-pablo-ruth-small.md deleted file mode 100644 index 3ec8d2576b3..00000000000 --- a/changelogs/unreleased/4271-pablo-ruth-small.md +++ /dev/null @@ -1 +0,0 @@ -Adds a new Ingress annotation, `projectcontour.io/tls-cert-namespace`, to allow [TLS Certificate Delegation](https://projectcontour.io/docs/main/config/tls-delegation/) to be used with Ingress v1. \ No newline at end of file diff --git a/changelogs/unreleased/4273-sunjayBhatia-small.md b/changelogs/unreleased/4273-sunjayBhatia-small.md deleted file mode 100644 index 11beca28351..00000000000 --- a/changelogs/unreleased/4273-sunjayBhatia-small.md +++ /dev/null @@ -1 +0,0 @@ -Bump Envoy to v1.21.0. See [release notes](https://www.envoyproxy.io/docs/envoy/v1.21.0/version_history/current). diff --git a/changelogs/unreleased/4279-sunjayBhatia-docs.md b/changelogs/unreleased/4279-sunjayBhatia-docs.md deleted file mode 100644 index e4526eaa92c..00000000000 --- a/changelogs/unreleased/4279-sunjayBhatia-docs.md +++ /dev/null @@ -1 +0,0 @@ -Adds documentation for header manipulation when using Ingress v1 resources. diff --git a/changelogs/unreleased/4281-sunjayBhatia-small.md b/changelogs/unreleased/4281-sunjayBhatia-small.md deleted file mode 100644 index a0994547d60..00000000000 --- a/changelogs/unreleased/4281-sunjayBhatia-small.md +++ /dev/null @@ -1 +0,0 @@ -Fixes bug in certgen error handling when writing certs to kubernetes. diff --git a/changelogs/unreleased/4287-skriss-small.md b/changelogs/unreleased/4287-skriss-small.md deleted file mode 100644 index 28cf8ca31b8..00000000000 --- a/changelogs/unreleased/4287-skriss-small.md +++ /dev/null @@ -1 +0,0 @@ -Fixes a bug where the global headers policy `ApplyToIngress` field was being ignored, causing Ingresses never to have the global headers policy applied. diff --git a/changelogs/unreleased/4288-skriss-docs.md b/changelogs/unreleased/4288-skriss-docs.md deleted file mode 100644 index 1eb29f8610c..00000000000 --- a/changelogs/unreleased/4288-skriss-docs.md +++ /dev/null @@ -1 +0,0 @@ -Revise the "Getting Started" guide for clarity and to cover more types of clusters and ways of installing Contour. diff --git a/site/config.yaml b/site/config.yaml index 7e90512e994..2d895a6e6fa 100644 --- a/site/config.yaml +++ b/site/config.yaml @@ -28,7 +28,7 @@ params: twitter_url: "https://twitter.com/projectcontour" github_url: "https://github.com/projectcontour/contour" slack_url: "https://kubernetes.slack.com/messages/contour" - latest_version: v1.19.1 + latest_version: v1.20.0 use_advanced_docs: true docs_right_sidebar: true docs_search: true @@ -37,6 +37,7 @@ params: docs_versioning: true docs_versions: - main + - v1.20.0 - v1.19.1 - v1.19.0 - v1.18.3 diff --git a/site/content/docs/v1.20.0/_index.md b/site/content/docs/v1.20.0/_index.md new file mode 100644 index 00000000000..d935b2b81ba --- /dev/null +++ b/site/content/docs/v1.20.0/_index.md @@ -0,0 +1,47 @@ +--- +cascade: + layout: docs + version: v1.20.0 +--- + +## Overview +Contour is an Ingress controller for Kubernetes that works by deploying the [Envoy proxy][1] as a reverse proxy and load balancer. +Contour supports dynamic configuration updates out of the box while maintaining a lightweight profile. + +## Philosophy +- Follow an opinionated approach which allows us to better serve most users +- Design Contour to serve both the cluster administrator and the application developer +- Use our experience with ingress to define reasonable defaults for both cluster administrators and application developers. +- Meet users where they are by understanding and adapting Contour to their use cases + +See the full [Contour Philosophy][8] page. + +## Why Contour? +Contour bridges other solution gaps in several ways: +- Dynamically update the ingress configuration with minimal dropped connections +- Safely support multiple types of ingress config in multi-team Kubernetes clusters + - [Ingress/v1][10] + - [HTTPProxy (Contour custom resource)][2] + - [Gateway API][9] +- Cleanly integrate with the Kubernetes object model + +## Prerequisites +Contour is tested with Kubernetes clusters running version [1.20 and later][4]. + +## Get started +Getting started with Contour is as simple as one command. +See the [Getting Started][3] document. + +## Troubleshooting +If you encounter issues review the [troubleshooting][5] page, [file an issue][6], or talk to us on the [#contour channel][7] on Kubernetes slack. + +[1]: https://www.envoyproxy.io/ +[2]: config/fundamentals.md +[3]: /getting-started +[4]: /resources/compatibility-matrix.md +[5]: /docs/main/troubleshooting +[6]: https://github.com/projectcontour/contour/issues +[7]: https://kubernetes.slack.com/messages/contour +[8]: /resources/philosophy +[9]: /guides/gateway-api-v1alpha2 +[10]: /docs/{{< param version >}}/config/ingress \ No newline at end of file diff --git a/site/content/docs/v1.20.0/architecture.md b/site/content/docs/v1.20.0/architecture.md new file mode 100644 index 00000000000..4dbba886863 --- /dev/null +++ b/site/content/docs/v1.20.0/architecture.md @@ -0,0 +1,39 @@ +# Contour Architecture + +The Contour Ingress controller is a collaboration between: + +* Envoy, which provides the high performance reverse proxy. +* Contour, which acts as a management server for Envoy and provides it with configuration. + +These containers are deployed separately, Contour as a Deployment and Envoy as a Daemonset, although other configurations are possible. + +In the Envoy Pods, Contour runs as an initcontainer in `bootstrap` mode and writes a bootstrap configuration to a temporary volume. +This volume is passed to the Envoy container and directs Envoy to treat Contour as its [management server][1]. + +After initialisation is complete, the Envoy container starts, retrieves the bootstrap configuration written by Contour's `bootstrap` mode, and establishes a GRPC session with Contour to receive configuration. + +Envoy will gracefully retry if the management server is unavailable, which removes any container startup ordering issues. + +Contour is a client of the Kubernetes API. +Contour watches Ingress, HTTPProxy, Secret, Service, and Endpoint objects, and acts as the management server for its Envoy sibling by translating its cache of objects into the relevant JSON stanzas: Service objects for CDS, Ingress for RDS, Endpoint objects for EDS, and so on). + +The transfer of information from Kubernetes to Contour is by watching the API with the SharedInformer framework. + +Kubernetes readiness probes are configured to check whether Envoy is ready to accept connections. +The Envoy readiness probe sends GET requests to `/ready` in Envoy's administration endpoint. + +For Contour, a liveness probe checks the `/healthz` running on the Pod's metrics port. +Readiness probe is a TCP check that the gRPC port is open. + +## Diagram +Below are a couple of high level architectural diagrams of how Contour works inside a Kubernetes cluster as well as showing the data path of a request to a backend pod. + +A request to `projectcontour.io/blog` gets routed via a load balancer to an instance of an Envoy proxy which then sends the request to a pod. + +![architectural overview][2] + +![architectural overview 2][3] + +[1]: https://www.envoyproxy.io/docs/envoy/v1.13.0/api-docs/xds_protocol +[2]: ../img/archoverview.png +[3]: ../img/contour_deployment_in_k8s.png diff --git a/site/content/docs/v1.20.0/config/access-logging.md b/site/content/docs/v1.20.0/config/access-logging.md new file mode 100644 index 00000000000..0ece9dd0df3 --- /dev/null +++ b/site/content/docs/v1.20.0/config/access-logging.md @@ -0,0 +1,126 @@ +# Access Logging + +## Overview + +Contour allows you to control Envoy's access logging. +By default, HTTP and HTTPS access logs are written to `/dev/stdout` by the Envoy containers and look like following: + +``` +[2021-04-14T16:36:00.361Z] "GET /foo HTTP/1.1" 200 - 0 463 6 3 "-" "HTTPie/1.0.3" "837aa8dc-344f-4faa-b7d5-c9cce1028519" "localhost:8080" "127.0.0.1:8081" +``` + +The detailed description of each field can be found in [Envoy access logging documentation][7]. + + +## Customizing Access Log Destination and Formats + +You can change the destination file where the access log is written by using Contour [command line parameters][1] `--envoy-http-access-log` and `--envoy-https-access-log`. + +The access log can take two different formats, both can be customized + +* Text based access logs, like shown in the example above. +* Structured JSON logging. + +### Text Based Access Logging + +Ensure that you have selected `envoy` as the access log format. +Note that this is the default format if the parameters are not given. + +- Add `--accesslog-format=envoy` to your Contour startup line, or +- Add `accesslog-format: envoy` to your configuration file. + +Customize the access log format by defining `accesslog-format-string` in your configuration file. + +```yaml +accesslog-format-string: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\"\n" +``` +After restarting Contour and successful validation of the configuration, the new format will take effect in a short while. + +Refer to [Envoy access logging documentation][7] for the description of the command operators, and note that the format string needs to end in a linefeed `\n`. + +### Structured JSON Logging + +Contour allows you to choose from a set of JSON fields that will be expanded into Envoy templates and sent to Envoy. +There is a default set of fields if you enable JSON logging, and you may customize which fields you log. + +The list of available fields are discoverable in the following objects: +- [jsonFields][2] are fields that have built in mappings to commonly used envoy operators. +- [envoySimpleOperators][3] are the names of simple envoy operators that don't require arguments, they are case-insensitive when configured. +- [envoyComplexOperators][4] are the names of complex envoy operators that require arguments. + +The default list of fields is available at [DefaultFields][5]. + +#### Enabling the Feature + +To enable the feature you have two options: + +- Add `--accesslog-format=json` to your Contour startup line. +- Add `accesslog-format: json` to your configuration file. + +Without any further customization, the [default fields][5] will be used. + +#### Customizing Logged Fields + +To customize the logged fields, add a `json-fields` list of strings to your configuration file. +If the `json-fields` key is not specified, the [default fields][5] will be configured. + +To use a value from [jsonFields][2] or [envoySimpleOperators][3], simply include the name of the value in the list of strings. +The jsonFields are case-sensitive, but envoySimpleOperators are not. + +To use [envoyComplexOperators][4] or to use alternative field names, specify strings as key/value pairs like `"fieldName=%OPERATOR(...)%"`. + +Unknown field names in non key/value fields will result in validation errors, as will unknown Envoy operators in key/value fields. +Note that the `DYNAMIC_METADATA` and `FILTER_STATE` Envoy logging operators are not supported at this time due to the complexity of their validation. + +See the [example config file][6] to see this used in context. + +#### Sample Configuration File + +Here is a sample config: + +```yaml +accesslog-format: json +json-fields: + - "@timestamp" + - "authority" + - "bytes_received" + - "bytes_sent" + - "customer_id=%REQ(X-CUSTOMER-ID)%" + - "downstream_local_address" + - "downstream_remote_address" + - "duration" + - "method" + - "path" + - "protocol" + - "request_id" + - "requested_server_name" + - "response_code" + - "response_flags" + - "uber_trace_id" + - "upstream_cluster" + - "upstream_host" + - "upstream_local_address" + - "upstream_service_time" + - "user_agent" + - "x_forwarded_for" +``` + +## Using Access Log Formatter Extensions + +Envoy allows implementing custom access log command operators as extensions. +Following extensions are supported by Contour: + +| Command operator | Description | +|------------------|-------------| +| [REQ_WITHOUT_QUERY][8] | Works the same way as REQ except that it will remove the query string. It is used to avoid logging any sensitive information into the access log. | + + + +[1]: ../configuration#serve-flags +[2]: https://github.com/search?q=jsonFields+repo%3Aprojectcontour%2Fcontour+path%3A%2Fpkg%2Fconfig+filename%3Aaccesslog.go&type=Code +[3]: https://github.com/search?q=envoySimpleOperators+repo%3Aprojectcontour%2Fcontour+path%3A%2Fpkg%2Fconfig+filename%3Aaccesslog.go&type=Code +[4]: https://github.com/search?q=envoyComplexOperators+repo%3Aprojectcontour%2Fcontour+path%3A%2Fpkg%2Fconfig+filename%3Aaccesslog.go&type=Code +[5]: https://github.com/search?q=DefaultFields+repo%3Aprojectcontour%2Fcontour+path%3A%2Fpkg%2Fconfig+filename%3Aaccesslog.go&type=Code +[6]: {{< param github_url >}}/tree/{{< param latest_version >}}/examples/contour/01-contour-config.yaml +[7]: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage +[8]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/formatter/req_without_query/v3/req_without_query.proto diff --git a/site/content/docs/v1.20.0/config/annotations.md b/site/content/docs/v1.20.0/config/annotations.md new file mode 100644 index 00000000000..71ac5efcc7f --- /dev/null +++ b/site/content/docs/v1.20.0/config/annotations.md @@ -0,0 +1,93 @@ +# Annotations Reference + + + +Annotations are used in Ingress Controllers to configure features that are not covered by the Kubernetes Ingress API. + +Some of the features that have been historically configured via annotations are supported as first-class features in Contour's [HTTPProxy API][15], which provides a more robust configuration interface over annotations. + +However, Contour still supports a number of annotations on the Ingress resources. + +## Standard Kubernetes Ingress annotations + +The following Kubernetes annotations are supported on `Ingress` objects: + +### Ingress Class + +The Ingress class annotation can be used to specify which Ingress controller should serve a particular Ingress object. +This annotation may be specified as the standard `kubernetes.io/ingress.class` or a Contour-specific `projectcontour.io/ingress.class`. +In both cases, they will behave as follows, by default: + +* If not set, then all Ingress controllers serve the Ingress. +* If specified as `kubernetes.io/ingress.class: contour`, then Contour serves the Ingress. +* If any other value, Contour ignores the Ingress definition. + +You can override the default class `contour` by providing the `--ingress-class-name` flag to Contour. +This can be useful while you are migrating from another controller, or if you need multiple instances of Contour. +If you do this, the behavior is as follows: +* If the annotation is not set, Contour will ignore the Ingress. +* If the annotation is set to any value other than the one passed to the `--ingress-class-name` flag, Contour will ignore the Ingress. +* If the annotation matches the value that you passed to `--ingress-class-name` flag, Contour will serve the Ingress. + +This same logic applies for these annotations on HTTPProxy objects. + +_Note: Both `Ingress` and `HTTPProxy` now have an `IngressClassName` field in their spec. Going forward this is the preferred way to specify an ingress class, rather than using an annotation. If both the annotation and the spec field are specified on an object, the annotation takes preference for backwards compatibility._ + +### Other annotations + + - `ingress.kubernetes.io/force-ssl-redirect`: Requires TLS/SSL for the Ingress to Envoy by setting the [Envoy virtual host option require_tls][16]. + - `kubernetes.io/ingress.allow-http`: Instructs Contour to not create an Envoy HTTP route for the virtual host. The Ingress exists only for HTTPS requests. Specify `"false"` for Envoy to mark the endpoint as HTTPS only. All other values are ignored. + +The `ingress.kubernetes.io/force-ssl-redirect` annotation takes precedence over `kubernetes.io/ingress.allow-http`. If they are set to `"true"` and `"false"` respectively, Contour *will* create an Envoy HTTP route for the Virtual host, and set the `require_tls` virtual host option. + +## Contour specific Ingress annotations + + - `projectcontour.io/ingress.class`: The Ingress class that should interpret and serve the Ingress. See the [main Ingress class annotation section](#ingress-class) for more details. + - `projectcontour.io/num-retries`: [The maximum number of retries][1] Envoy should make before abandoning and returning an error to the client. Applies only if `projectcontour.io/retry-on` is specified. Set to -1 to disable retries. + - `projectcontour.io/per-try-timeout`: [The timeout per retry attempt][2], if there should be one. Applies only if `projectcontour.io/retry-on` is specified. + - `projectcontour.io/response-timeout`: [The Envoy HTTP route timeout][3], specified as a [golang duration][4]. By default, Envoy has a 15 second timeout for a backend service to respond. Set this to `infinity` to specify that Envoy should never timeout the connection to the backend. Note that the value `0s` / zero has special semantics for Envoy. + - `projectcontour.io/retry-on`: [The conditions for Envoy to retry a request][5]. See also [possible values and their meanings for `retry-on`][6]. + - `projectcontour.io/tls-minimum-protocol-version`: [The minimum TLS protocol version][7] the TLS listener should support. Valid options are `1.3`, `1.2` (default), `1.1`. + - `projectcontour.io/websocket-routes`: [The routes supporting websocket protocol][8], the annotation value contains a list of route paths separated by a comma that must match with the ones defined in the `Ingress` definition. Defaults to Envoy's default behavior which is `use_websocket` to `false`. + - `projectcontour.io/tls-cert-namespace`: The namespace where all TLS secrets of this Ingress are searched. This is necessary to use [TLS Certificate Delegation][18] with Ingress v1 because the slash notation (ex: different-ns/app-cert) used by HTTPProxy and Ingress v1beta1 is not accepted. See [this issue][19] for details. + +## Contour specific Service annotations + +A [Kubernetes Service][9] maps to an [Envoy Cluster][10]. Envoy clusters have many settings to control specific behaviors. These annotations allow access to some of those settings. + +- `projectcontour.io/max-connections`: [The maximum number of connections][11] that a single Envoy instance allows to the Kubernetes Service; defaults to 1024. +- `projectcontour.io/max-pending-requests`: [The maximum number of pending requests][13] that a single Envoy instance allows to the Kubernetes Service; defaults to 1024. +- `projectcontour.io/max-requests`: [The maximum parallel requests][13] a single Envoy instance allows to the Kubernetes Service; defaults to 1024 +- `projectcontour.io/max-retries`: [The maximum number of parallel retries][14] a single Envoy instance allows to the Kubernetes Service; defaults to 3. This is independent of the per-Kubernetes Ingress number of retries (`projectcontour.io/num-retries`) and retry-on (`projectcontour.io/retry-on`), which control whether retries are attempted and how many times a single request can retry. +- `projectcontour.io/upstream-protocol.{protocol}` : The protocol used to proxy requests to the upstream service. + The annotation value contains a comma-separated list of port names and/or numbers that must match with the ones defined in the `Service` definition. + This value can also be specified in the `spec.routes.services[].protocol` field on the HTTPProxy object, where it takes precedence over the Service annotation. + Supported protocol names are: `h2`, `h2c`, and `tls`: + - The `tls` protocol allows for requests which terminate at Envoy to proxy via TLS to the upstream. + This protocol should be used for HTTP/1.1 services over TLS. + _Note that validating the upstream TLS certificate requires additionally setting the [validation][17] field._ + - The `h2` protocol proxies requests to the upstream using HTTP/2 over TLS. + - The `h2c` protocol proxies requests to the the upstream using cleartext HTTP/2. + +## Contour specific HTTPProxy annotations +- `projectcontour.io/ingress.class`: The Ingress class that should interpret and serve the HTTPProxy. See the [main Ingress class annotation section](#ingress-class) for more details. + +[1]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries +[2]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-retrypolicy-retry-on +[3]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-timeout +[4]: https://golang.org/pkg/time/#ParseDuration +[5]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-retrypolicy-retry-on +[6]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-retry-on +[7]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/common.proto.html#extensions-transport-sockets-tls-v3-tlsparameters +[8]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-upgrade-configs +[9]: https://kubernetes.io/docs/concepts/services-networking/service/ +[10]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/intro/terminology +[11]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/circuit_breaker.proto#envoy-v3-api-field-config-cluster-v3-circuitbreakers-thresholds-max-connections +[12]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/circuit_breaker.proto#envoy-v3-api-field-config-cluster-v3-circuitbreakers-thresholds-max-pending-requests +[13]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/circuit_breaker.proto#envoy-v3-api-field-config-cluster-v3-circuitbreakers-thresholds-max-requests +[14]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/circuit_breaker.proto#envoy-v3-api-field-config-cluster-v3-circuitbreakers-thresholds-max-retries +[15]: fundamentals.md +[16]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-virtualhost-require-tls +[17]: api/#projectcontour.io/v1.UpstreamValidation +[18]: ../config/tls-delegation/ +[19]: https://github.com/projectcontour/contour/issues/3544 \ No newline at end of file diff --git a/site/content/docs/v1.20.0/config/api-reference.html b/site/content/docs/v1.20.0/config/api-reference.html new file mode 100644 index 00000000000..dad3b22eafe --- /dev/null +++ b/site/content/docs/v1.20.0/config/api-reference.html @@ -0,0 +1,6167 @@ +

Packages:

+ +

projectcontour.io/v1

+

+

Package v1 holds the specification for the projectcontour.io Custom Resource Definitions (CRDs).

+

In building this CRD, we’ve inadvertently overloaded the word “Condition”, so we’ve tried to make +this spec clear as to which types of condition are which.

+

MatchConditions are used by Routes and Includes to specify rules to match requests against for either +routing or inclusion.

+

DetailedConditions are used in the Status of these objects to hold information about the relevant +state of the object and the world around it.

+

SubConditions are used underneath DetailedConditions to give more detail to errors or warnings.

+

+Resource Types: + +

HTTPProxy +

+

+

HTTPProxy is an Ingress CRD specification.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion +
+string
+ +projectcontour.io/v1 + +
+kind +
+string +
HTTPProxy
+metadata +
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec +
+ + +HTTPProxySpec + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+virtualhost +
+ + +VirtualHost + + +
+(Optional) +

Virtualhost appears at most once. If it is present, the object is considered +to be a “root” HTTPProxy.

+
+routes +
+ + +[]Route + + +
+(Optional) +

Routes are the ingress routes. If TCPProxy is present, Routes is ignored.

+
+tcpproxy +
+ + +TCPProxy + + +
+(Optional) +

TCPProxy holds TCP proxy information.

+
+includes +
+ + +[]Include + + +
+(Optional) +

Includes allow for specific routing configuration to be included from another HTTPProxy, +possibly in another namespace.

+
+ingressClassName +
+ +string + +
+(Optional) +

IngressClassName optionally specifies the ingress class to use for this +HTTPProxy. This replaces the deprecated kubernetes.io/ingress.class +annotation. For backwards compatibility, when that annotation is set, it +is given precedence over this field.

+
+
+status +
+ + +HTTPProxyStatus + + +
+(Optional) +

Status is a container for computed information about the HTTPProxy.

+
+

TLSCertificateDelegation +

+

+

TLSCertificateDelegation is an TLS Certificate Delegation CRD specification. +See design/tls-certificate-delegation.md for details.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion +
+string
+ +projectcontour.io/v1 + +
+kind +
+string +
TLSCertificateDelegation
+metadata +
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec +
+ + +TLSCertificateDelegationSpec + + +
+
+
+ + + + + +
+delegations +
+ + +[]CertificateDelegation + + +
+
+
+status +
+ + +TLSCertificateDelegationStatus + + +
+(Optional) +
+

AuthorizationPolicy +

+

+(Appears on: +AuthorizationServer, +Route) +

+

+

AuthorizationPolicy modifies how client requests are authenticated.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+disabled +
+ +bool + +
+(Optional) +

When true, this field disables client request authentication +for the scope of the policy.

+
+context +
+ +map[string]string + +
+(Optional) +

Context is a set of key/value pairs that are sent to the +authentication server in the check request. If a context +is provided at an enclosing scope, the entries are merged +such that the inner scope overrides matching keys from the +outer scope.

+
+

AuthorizationServer +

+

+(Appears on: +VirtualHost) +

+

+

AuthorizationServer configures an external server to authenticate +client requests. The external server must implement the v3 Envoy +external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto).

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+extensionRef +
+ + +ExtensionServiceReference + + +
+

ExtensionServiceRef specifies the extension resource that will authorize client requests.

+
+authPolicy +
+ + +AuthorizationPolicy + + +
+(Optional) +

AuthPolicy sets a default authorization policy for client requests. +This policy will be used unless overridden by individual routes.

+
+responseTimeout +
+ +string + +
+(Optional) +

ResponseTimeout configures maximum time to wait for a check response from the authorization server. +Timeout durations are expressed in the Go Duration format. +Valid time units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”. +The string “infinity” is also a valid input and specifies no timeout.

+
+failOpen +
+ +bool + +
+(Optional) +

If FailOpen is true, the client request is forwarded to the upstream service +even if the authorization server fails to respond. This field should not be +set in most cases. It is intended for use only while migrating applications +from internal authorization to Contour external authorization.

+
+withRequestBody +
+ + +AuthorizationServerBufferSettings + + +
+(Optional) +

WithRequestBody specifies configuration for sending the client request’s body to authorization server.

+
+

AuthorizationServerBufferSettings +

+

+(Appears on: +AuthorizationServer) +

+

+

AuthorizationServerBufferSettings enables ExtAuthz filter to buffer client request data and send it as part of authorization request

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+maxRequestBytes +
+ +uint32 + +
+(Optional) +

MaxRequestBytes sets the maximum size of message body ExtAuthz filter will hold in-memory.

+
+allowPartialMessage +
+ +bool + +
+(Optional) +

If AllowPartialMessage is true, then Envoy will buffer the body until MaxRequestBytes are reached.

+
+packAsBytes +
+ +bool + +
+(Optional) +

If PackAsBytes is true, the body sent to Authorization Server is in raw bytes.

+
+

CORSHeaderValue +(string alias)

+

+(Appears on: +CORSPolicy) +

+

+

CORSHeaderValue specifies the value of the string headers returned by a cross-domain request.

+

+

CORSPolicy +

+

+(Appears on: +VirtualHost) +

+

+

CORSPolicy allows setting the CORS policy

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+allowCredentials +
+ +bool + +
+(Optional) +

Specifies whether the resource allows credentials.

+
+allowOrigin +
+ +[]string + +
+

AllowOrigin specifies the origins that will be allowed to do CORS requests. “*” means +allow any origin.

+
+allowMethods +
+ + +[]CORSHeaderValue + + +
+

AllowMethods specifies the content for the access-control-allow-methods header.

+
+allowHeaders +
+ + +[]CORSHeaderValue + + +
+(Optional) +

AllowHeaders specifies the content for the access-control-allow-headers header.

+
+exposeHeaders +
+ + +[]CORSHeaderValue + + +
+(Optional) +

ExposeHeaders Specifies the content for the access-control-expose-headers header.

+
+maxAge +
+ +string + +
+(Optional) +

MaxAge indicates for how long the results of a preflight request can be cached. +MaxAge durations are expressed in the Go Duration format. +Valid time units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”. +Only positive values are allowed while 0 disables the cache requiring a preflight OPTIONS +check for all cross-origin requests.

+
+

CertificateDelegation +

+

+(Appears on: +TLSCertificateDelegationSpec) +

+

+

CertificateDelegation maps the authority to reference a secret +in the current namespace to a set of namespaces.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+secretName +
+ +string + +
+

required, the name of a secret in the current namespace.

+
+targetNamespaces +
+ +[]string + +
+

required, the namespaces the authority to reference the +the secret will be delegated to. +If TargetNamespaces is nil or empty, the CertificateDelegation’ +is ignored. If the TargetNamespace list contains the character, “*” +the secret will be delegated to all namespaces.

+
+

CookieDomainRewrite +

+

+(Appears on: +CookieRewritePolicy) +

+

+

+ + + + + + + + + + + + + +
FieldDescription
+value +
+ +string + +
+

Value is the value to rewrite the Domain attribute to. +For now this is required.

+
+

CookiePathRewrite +

+

+(Appears on: +CookieRewritePolicy) +

+

+

+ + + + + + + + + + + + + +
FieldDescription
+value +
+ +string + +
+

Value is the value to rewrite the Path attribute to. +For now this is required.

+
+

CookieRewritePolicy +

+

+(Appears on: +Route, +Service) +

+

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+

Name is the name of the cookie for which attributes will be rewritten.

+
+pathRewrite +
+ + +CookiePathRewrite + + +
+(Optional) +

PathRewrite enables rewriting the Set-Cookie Path element. +If not set, Path will not be rewritten.

+
+domainRewrite +
+ + +CookieDomainRewrite + + +
+(Optional) +

DomainRewrite enables rewriting the Set-Cookie Domain element. +If not set, Domain will not be rewritten.

+
+secure +
+ +bool + +
+(Optional) +

Secure enables rewriting the Set-Cookie Secure element. +If not set, Secure attribute will not be rewritten.

+
+sameSite +
+ +string + +
+(Optional) +

SameSite enables rewriting the Set-Cookie SameSite element. +If not set, SameSite attribute will not be rewritten.

+
+

DetailedCondition +

+

+(Appears on: +HTTPProxyStatus, +TLSCertificateDelegationStatus, +ContourConfigurationStatus, +ContourDeploymentStatus, +ExtensionServiceStatus) +

+

+

DetailedCondition is an extension of the normal Kubernetes conditions, with two extra +fields to hold sub-conditions, which provide more detailed reasons for the state (True or False) +of the condition.

+

errors holds information about sub-conditions which are fatal to that condition and render its state False.

+

warnings holds information about sub-conditions which are not fatal to that condition and do not force the state to be False.

+

Remember that Conditions have a type, a status, and a reason.

+

The type is the type of the condition, the most important one in this CRD set is Valid. +Valid is a positive-polarity condition: when it is status: true there are no problems.

+

In more detail, status: true means that the object is has been ingested into Contour with no errors. +warnings may still be present, and will be indicated in the Reason field. There must be zero entries in the errors +slice in this case.

+

Valid, status: false means that the object has had one or more fatal errors during processing into Contour. +The details of the errors will be present under the errors field. There must be at least one error in the errors +slice if status is false.

+

For DetailedConditions of types other than Valid, the Condition must be in the negative polarity. +When they have status true, there is an error. There must be at least one entry in the errors Subcondition slice. +When they have status false, there are no serious errors, and there must be zero entries in the errors slice. +In either case, there may be entries in the warnings slice.

+

Regardless of the polarity, the reason and message fields must be updated with either the detail of the reason +(if there is one and only one entry in total across both the errors and warnings slices), or +MultipleReasons if there is more than one entry.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+Condition +
+ + +Kubernetes meta/v1.Condition + + +
+

+(Members of Condition are embedded into this type.) +

+
+errors +
+ + +[]SubCondition + + +
+(Optional) +

Errors contains a slice of relevant error subconditions for this object.

+

Subconditions are expected to appear when relevant (when there is a error), and disappear when not relevant. +An empty slice here indicates no errors.

+
+warnings +
+ + +[]SubCondition + + +
+(Optional) +

Warnings contains a slice of relevant warning subconditions for this object.

+

Subconditions are expected to appear when relevant (when there is a warning), and disappear when not relevant. +An empty slice here indicates no warnings.

+
+

DownstreamValidation +

+

+(Appears on: +TLS) +

+

+

DownstreamValidation defines how to verify the client certificate.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+caSecret +
+ +string + +
+(Optional) +

Name of a Kubernetes secret that contains a CA certificate bundle. +The client certificate must validate against the certificates in the bundle. +If specified and SkipClientCertValidation is true, client certificates will +be required on requests.

+
+skipClientCertValidation +
+ +bool + +
+(Optional) +

SkipClientCertValidation disables downstream client certificate +validation. Defaults to false. This field is intended to be used in +conjunction with external authorization in order to enable the external +authorization server to validate client certificates. When this field +is set to true, client certificates are requested but not verified by +Envoy. If CACertificate is specified, client certificates are required on +requests, but not verified. If external authorization is in use, they are +presented to the external authorization server.

+
+

ExtensionServiceReference +

+

+(Appears on: +AuthorizationServer) +

+

+

ExtensionServiceReference names an ExtensionService resource.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion +
+ +string + +
+(Optional) +

API version of the referent. +If this field is not specified, the default “projectcontour.io/v1alpha1” will be used

+
+namespace +
+ +string + +
+(Optional) +

Namespace of the referent. +If this field is not specifies, the namespace of the resource that targets the referent will be used.

+

More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

+
+name +
+ +string + +
+

Name of the referent.

+

More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

+
+

GenericKeyDescriptor +

+

+(Appears on: +RateLimitDescriptorEntry) +

+

+

GenericKeyDescriptor defines a descriptor entry with a static key and +value.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+key +
+ +string + +
+(Optional) +

Key defines the key of the descriptor entry. If not set, the +key is set to “generic_key”.

+
+value +
+ +string + +
+

Value defines the value of the descriptor entry.

+
+

GlobalRateLimitPolicy +

+

+(Appears on: +RateLimitPolicy) +

+

+

GlobalRateLimitPolicy defines global rate limiting parameters.

+

+ + + + + + + + + + + + + +
FieldDescription
+descriptors +
+ + +[]RateLimitDescriptor + + +
+

Descriptors defines the list of descriptors that will +be generated and sent to the rate limit service. Each +descriptor contains 1+ key-value pair entries.

+
+

HTTPHealthCheckPolicy +

+

+(Appears on: +Route) +

+

+

HTTPHealthCheckPolicy defines health checks on the upstream service.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+path +
+ +string + +
+

HTTP endpoint used to perform health checks on upstream service

+
+host +
+ +string + +
+

The value of the host header in the HTTP health check request. +If left empty (default value), the name “contour-envoy-healthcheck” +will be used.

+
+intervalSeconds +
+ +int64 + +
+(Optional) +

The interval (seconds) between health checks

+
+timeoutSeconds +
+ +int64 + +
+(Optional) +

The time to wait (seconds) for a health check response

+
+unhealthyThresholdCount +
+ +int64 + +
+(Optional) +

The number of unhealthy health checks required before a host is marked unhealthy

+
+healthyThresholdCount +
+ +int64 + +
+(Optional) +

The number of healthy health checks required before a host is marked healthy

+
+

HTTPProxySpec +

+

+(Appears on: +HTTPProxy) +

+

+

HTTPProxySpec defines the spec of the CRD.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+virtualhost +
+ + +VirtualHost + + +
+(Optional) +

Virtualhost appears at most once. If it is present, the object is considered +to be a “root” HTTPProxy.

+
+routes +
+ + +[]Route + + +
+(Optional) +

Routes are the ingress routes. If TCPProxy is present, Routes is ignored.

+
+tcpproxy +
+ + +TCPProxy + + +
+(Optional) +

TCPProxy holds TCP proxy information.

+
+includes +
+ + +[]Include + + +
+(Optional) +

Includes allow for specific routing configuration to be included from another HTTPProxy, +possibly in another namespace.

+
+ingressClassName +
+ +string + +
+(Optional) +

IngressClassName optionally specifies the ingress class to use for this +HTTPProxy. This replaces the deprecated kubernetes.io/ingress.class +annotation. For backwards compatibility, when that annotation is set, it +is given precedence over this field.

+
+

HTTPProxyStatus +

+

+(Appears on: +HTTPProxy) +

+

+

HTTPProxyStatus reports the current state of the HTTPProxy.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+currentStatus +
+ +string + +
+(Optional) +
+description +
+ +string + +
+(Optional) +
+loadBalancer +
+ + +Kubernetes core/v1.LoadBalancerStatus + + +
+(Optional) +

LoadBalancer contains the current status of the load balancer.

+
+conditions +
+ + +[]DetailedCondition + + +
+(Optional) +

Conditions contains information about the current status of the HTTPProxy, +in an upstream-friendly container.

+

Contour will update a single condition, Valid, that is in normal-true polarity. +That is, when currentStatus is valid, the Valid condition will be status: true, +and vice versa.

+

Contour will leave untouched any other Conditions set in this block, +in case some other controller wants to add a Condition.

+

If you are another controller owner and wish to add a condition, you should +namespace your condition with a label, like controller.domain.com/ConditionName.

+
+

HTTPRequestRedirectPolicy +

+

+(Appears on: +Route) +

+

+

HTTPRequestRedirectPolicy defines configuration for redirecting a request.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+scheme +
+ +string + +
+(Optional) +

Scheme is the scheme to be used in the value of the Location +header in the response. +When empty, the scheme of the request is used.

+
+hostname +
+ +string + +
+(Optional) +

Hostname is the precise hostname to be used in the value of the Location +header in the response. +When empty, the hostname of the request is used. +No wildcards are allowed.

+
+port +
+ +int32 + +
+(Optional) +

Port is the port to be used in the value of the Location +header in the response. +When empty, port (if specified) of the request is used.

+
+statusCode +
+ +int + +
+(Optional) +

StatusCode is the HTTP status code to be used in response.

+
+path +
+ +string + +
+(Optional) +

Path allows for redirection to a different path from the +original on the request. The path must start with a +leading slash.

+

Note: Only one of Path or Prefix can be defined.

+
+prefix +
+ +string + +
+(Optional) +

Prefix defines the value to swap the matched prefix or path with. +The prefix must start with a leading slash.

+

Note: Only one of Path or Prefix can be defined.

+
+

HeaderHashOptions +

+

+(Appears on: +RequestHashPolicy) +

+

+

HeaderHashOptions contains options to configure a HTTP request header hash +policy, used in request attribute hash based load balancing.

+

+ + + + + + + + + + + + + +
FieldDescription
+headerName +
+ +string + +
+

HeaderName is the name of the HTTP request header that will be used to +calculate the hash key. If the header specified is not present on a +request, no hash will be produced.

+
+

HeaderMatchCondition +

+

+(Appears on: +MatchCondition, +RequestHeaderValueMatchDescriptor) +

+

+

HeaderMatchCondition specifies how to conditionally match against HTTP +headers. The Name field is required, but only one of the remaining +fields should be be provided.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+

Name is the name of the header to match against. Name is required. +Header names are case insensitive.

+
+present +
+ +bool + +
+(Optional) +

Present specifies that condition is true when the named header +is present, regardless of its value. Note that setting Present +to false does not make the condition true if the named header +is absent.

+
+notpresent +
+ +bool + +
+(Optional) +

NotPresent specifies that condition is true when the named header +is not present. Note that setting NotPresent to false does not +make the condition true if the named header is present.

+
+contains +
+ +string + +
+(Optional) +

Contains specifies a substring that must be present in +the header value.

+
+notcontains +
+ +string + +
+(Optional) +

NotContains specifies a substring that must not be present +in the header value.

+
+exact +
+ +string + +
+(Optional) +

Exact specifies a string that the header value must be equal to.

+
+notexact +
+ +string + +
+(Optional) +

NoExact specifies a string that the header value must not be +equal to. The condition is true if the header has any other value.

+
+

HeaderValue +

+

+(Appears on: +HeadersPolicy, +LocalRateLimitPolicy) +

+

+

HeaderValue represents a header name/value pair

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+

Name represents a key of a header

+
+value +
+ +string + +
+

Value represents the value of a header specified by a key

+
+

HeadersPolicy +

+

+(Appears on: +Route, +Service) +

+

+

HeadersPolicy defines how headers are managed during forwarding. +The Host header is treated specially and if set in a HTTP response +will be used as the SNI server name when forwarding over TLS. It is an +error to attempt to set the Host header in a HTTP response.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+set +
+ + +[]HeaderValue + + +
+(Optional) +

Set specifies a list of HTTP header values that will be set in the HTTP header. +If the header does not exist it will be added, otherwise it will be overwritten with the new value.

+
+remove +
+ +[]string + +
+(Optional) +

Remove specifies a list of HTTP header names to remove.

+
+

Include +

+

+(Appears on: +HTTPProxySpec) +

+

+

Include describes a set of policies that can be applied to an HTTPProxy in a namespace.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+

Name of the HTTPProxy

+
+namespace +
+ +string + +
+(Optional) +

Namespace of the HTTPProxy to include. Defaults to the current namespace if not supplied.

+
+conditions +
+ + +[]MatchCondition + + +
+(Optional) +

Conditions are a set of rules that are applied to included HTTPProxies. +In effect, they are added onto the Conditions of included HTTPProxy Route +structs. +When applied, they are merged using AND, with one exception: +There can be only one Prefix MatchCondition per Conditions slice. +More than one Prefix, or contradictory Conditions, will make the +include invalid.

+
+

LoadBalancerPolicy +

+

+(Appears on: +Route, +TCPProxy, +ExtensionServiceSpec) +

+

+

LoadBalancerPolicy defines the load balancing policy.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+strategy +
+ +string + +
+

Strategy specifies the policy used to balance requests +across the pool of backend pods. Valid policy names are +Random, RoundRobin, WeightedLeastRequest, Cookie, +and RequestHash. If an unknown strategy name is specified +or no policy is supplied, the default RoundRobin policy +is used.

+
+requestHashPolicies +
+ + +[]RequestHashPolicy + + +
+

RequestHashPolicies contains a list of hash policies to apply when the +RequestHash load balancing strategy is chosen. If an element of the +supplied list of hash policies is invalid, it will be ignored. If the +list of hash policies is empty after validation, the load balancing +strategy will fall back the the default RoundRobin.

+
+

LocalRateLimitPolicy +

+

+(Appears on: +RateLimitPolicy) +

+

+

LocalRateLimitPolicy defines local rate limiting parameters.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+requests +
+ +uint32 + +
+

Requests defines how many requests per unit of time should +be allowed before rate limiting occurs.

+
+unit +
+ +string + +
+

Unit defines the period of time within which requests +over the limit will be rate limited. Valid values are +“second”, “minute” and “hour”.

+
+burst +
+ +uint32 + +
+(Optional) +

Burst defines the number of requests above the requests per +unit that should be allowed within a short period of time.

+
+responseStatusCode +
+ +uint32 + +
+(Optional) +

ResponseStatusCode is the HTTP status code to use for responses +to rate-limited requests. Codes must be in the 400-599 range +(inclusive). If not specified, the Envoy default of 429 (Too +Many Requests) is used.

+
+responseHeadersToAdd +
+ + +[]HeaderValue + + +
+(Optional) +

ResponseHeadersToAdd is an optional list of response headers to +set when a request is rate-limited.

+
+

MatchCondition +

+

+(Appears on: +Include, +Route) +

+

+

MatchCondition are a general holder for matching rules for HTTPProxies. +One of Prefix or Header must be provided.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+prefix +
+ +string + +
+(Optional) +

Prefix defines a prefix match for a request.

+
+header +
+ + +HeaderMatchCondition + + +
+(Optional) +

Header specifies the header condition to match.

+
+

PathRewritePolicy +

+

+(Appears on: +Route) +

+

+

PathRewritePolicy specifies how a request URL path should be +rewritten. This rewriting takes place after a request is routed +and has no subsequent effects on the proxy’s routing decision. +No HTTP headers or body content is rewritten.

+

Exactly one field in this struct may be specified.

+

+ + + + + + + + + + + + + +
FieldDescription
+replacePrefix +
+ + +[]ReplacePrefix + + +
+(Optional) +

ReplacePrefix describes how the path prefix should be replaced.

+
+

RateLimitDescriptor +

+

+(Appears on: +GlobalRateLimitPolicy) +

+

+

RateLimitDescriptor defines a list of key-value pair generators.

+

+ + + + + + + + + + + + + +
FieldDescription
+entries +
+ + +[]RateLimitDescriptorEntry + + +
+

Entries is the list of key-value pair generators.

+
+

RateLimitDescriptorEntry +

+

+(Appears on: +RateLimitDescriptor) +

+

+

RateLimitDescriptorEntry is a key-value pair generator. Exactly +one field on this struct must be non-nil.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+genericKey +
+ + +GenericKeyDescriptor + + +
+(Optional) +

GenericKey defines a descriptor entry with a static key and value.

+
+requestHeader +
+ + +RequestHeaderDescriptor + + +
+(Optional) +

RequestHeader defines a descriptor entry that’s populated only if +a given header is present on the request. The descriptor key is static, +and the descriptor value is equal to the value of the header.

+
+requestHeaderValueMatch +
+ + +RequestHeaderValueMatchDescriptor + + +
+(Optional) +

RequestHeaderValueMatch defines a descriptor entry that’s populated +if the request’s headers match a set of 1+ match criteria. The +descriptor key is “header_match”, and the descriptor value is static.

+
+remoteAddress +
+ + +RemoteAddressDescriptor + + +
+(Optional) +

RemoteAddress defines a descriptor entry with a key of “remote_address” +and a value equal to the client’s IP address (from x-forwarded-for).

+
+

RateLimitPolicy +

+

+(Appears on: +Route, +VirtualHost) +

+

+

RateLimitPolicy defines rate limiting parameters.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+local +
+ + +LocalRateLimitPolicy + + +
+(Optional) +

Local defines local rate limiting parameters, i.e. parameters +for rate limiting that occurs within each Envoy pod as requests +are handled.

+
+global +
+ + +GlobalRateLimitPolicy + + +
+(Optional) +

Global defines global rate limiting parameters, i.e. parameters +defining descriptors that are sent to an external rate limit +service (RLS) for a rate limit decision on each request.

+
+

RemoteAddressDescriptor +

+

+(Appears on: +RateLimitDescriptorEntry) +

+

+

RemoteAddressDescriptor defines a descriptor entry with a key of +“remote_address” and a value equal to the client’s IP address +(from x-forwarded-for).

+

+

ReplacePrefix +

+

+(Appears on: +PathRewritePolicy) +

+

+

ReplacePrefix describes a path prefix replacement.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+prefix +
+ +string + +
+(Optional) +

Prefix specifies the URL path prefix to be replaced.

+

If Prefix is specified, it must exactly match the MatchCondition +prefix that is rendered by the chain of including HTTPProxies +and only that path prefix will be replaced by Replacement. +This allows HTTPProxies that are included through multiple +roots to only replace specific path prefixes, leaving others +unmodified.

+

If Prefix is not specified, all routing prefixes rendered +by the include chain will be replaced.

+
+replacement +
+ +string + +
+

Replacement is the string that the routing path prefix +will be replaced with. This must not be empty.

+
+

RequestHashPolicy +

+

+(Appears on: +LoadBalancerPolicy) +

+

+

RequestHashPolicy contains configuration for an individual hash policy +on a request attribute.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+terminal +
+ +bool + +
+

Terminal is a flag that allows for short-circuiting computing of a hash +for a given request. If set to true, and the request attribute specified +in the attribute hash options is present, no further hash policies will +be used to calculate a hash for the request.

+
+headerHashOptions +
+ + +HeaderHashOptions + + +
+(Optional) +

HeaderHashOptions should be set when request header hash based load +balancing is desired. It must be the only hash option field set, +otherwise this request hash policy object will be ignored.

+
+hashSourceIP +
+ +bool + +
+(Optional) +

HashSourceIP should be set to true when request source IP hash based +load balancing is desired. It must be the only hash option field set, +otherwise this request hash policy object will be ignored.

+
+

RequestHeaderDescriptor +

+

+(Appears on: +RateLimitDescriptorEntry) +

+

+

RequestHeaderDescriptor defines a descriptor entry that’s populated only +if a given header is present on the request. The value of the descriptor +entry is equal to the value of the header (if present).

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+headerName +
+ +string + +
+

HeaderName defines the name of the header to look for on the request.

+
+descriptorKey +
+ +string + +
+

DescriptorKey defines the key to use on the descriptor entry.

+
+

RequestHeaderValueMatchDescriptor +

+

+(Appears on: +RateLimitDescriptorEntry) +

+

+

RequestHeaderValueMatchDescriptor defines a descriptor entry that’s populated +if the request’s headers match a set of 1+ match criteria. The descriptor key +is “header_match”, and the descriptor value is statically defined.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+headers +
+ + +[]HeaderMatchCondition + + +
+

Headers is a list of 1+ match criteria to apply against the request +to determine whether to populate the descriptor entry or not.

+
+expectMatch +
+ +bool + +
+

ExpectMatch defines whether the request must positively match the match +criteria in order to generate a descriptor entry (i.e. true), or not +match the match criteria in order to generate a descriptor entry (i.e. false). +The default is true.

+
+value +
+ +string + +
+

Value defines the value of the descriptor entry.

+
+

RetryOn +(string alias)

+

+(Appears on: +RetryPolicy) +

+

+

RetryOn is a string type alias with validation to ensure that the value is valid.

+

+

RetryPolicy +

+

+(Appears on: +Route) +

+

+

RetryPolicy defines the attributes associated with retrying policy.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+count +
+ +int64 + +
+(Optional) +

NumRetries is maximum allowed number of retries. +If set to -1, then retries are disabled. +If set to 0 or not supplied, the value is set +to the Envoy default of 1.

+
+perTryTimeout +
+ +string + +
+(Optional) +

PerTryTimeout specifies the timeout per retry attempt. +Ignored if NumRetries is not supplied.

+
+retryOn +
+ + +[]RetryOn + + +
+(Optional) +

RetryOn specifies the conditions on which to retry a request.

+

Supported HTTP conditions:

+
    +
  • 5xx
  • +
  • gateway-error
  • +
  • reset
  • +
  • connect-failure
  • +
  • retriable-4xx
  • +
  • refused-stream
  • +
  • retriable-status-codes
  • +
  • retriable-headers
  • +
+

Supported gRPC conditions:

+
    +
  • cancelled
  • +
  • deadline-exceeded
  • +
  • internal
  • +
  • resource-exhausted
  • +
  • unavailable
  • +
+
+retriableStatusCodes +
+ +[]uint32 + +
+(Optional) +

RetriableStatusCodes specifies the HTTP status codes that should be retried.

+

This field is only respected when you include retriable-status-codes in the RetryOn field.

+
+

Route +

+

+(Appears on: +HTTPProxySpec) +

+

+

Route contains the set of routes for a virtual host.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+conditions +
+ + +[]MatchCondition + + +
+(Optional) +

Conditions are a set of rules that are applied to a Route. +When applied, they are merged using AND, with one exception: +There can be only one Prefix MatchCondition per Conditions slice. +More than one Prefix, or contradictory Conditions, will make the +route invalid.

+
+services +
+ + +[]Service + + +
+(Optional) +

Services are the services to proxy traffic.

+
+enableWebsockets +
+ +bool + +
+(Optional) +

Enables websocket support for the route.

+
+permitInsecure +
+ +bool + +
+(Optional) +

Allow this path to respond to insecure requests over HTTP which are normally +not permitted when a virtualhost.tls block is present.

+
+authPolicy +
+ + +AuthorizationPolicy + + +
+(Optional) +

AuthPolicy updates the authorization policy that was set +on the root HTTPProxy object for client requests that +match this route.

+
+timeoutPolicy +
+ + +TimeoutPolicy + + +
+(Optional) +

The timeout policy for this route.

+
+retryPolicy +
+ + +RetryPolicy + + +
+(Optional) +

The retry policy for this route.

+
+healthCheckPolicy +
+ + +HTTPHealthCheckPolicy + + +
+(Optional) +

The health check policy for this route.

+
+loadBalancerPolicy +
+ + +LoadBalancerPolicy + + +
+(Optional) +

The load balancing policy for this route.

+
+pathRewritePolicy +
+ + +PathRewritePolicy + + +
+(Optional) +

The policy for rewriting the path of the request URL +after the request has been routed to a Service.

+
+requestHeadersPolicy +
+ + +HeadersPolicy + + +
+(Optional) +

The policy for managing request headers during proxying.

+
+responseHeadersPolicy +
+ + +HeadersPolicy + + +
+(Optional) +

The policy for managing response headers during proxying. +Rewriting the ‘Host’ header is not supported.

+
+cookieRewritePolicies +
+ + +[]CookieRewritePolicy + + +
+(Optional) +

The policies for rewriting Set-Cookie header attributes. Note that +rewritten cookie names must be unique in this list. Order rewrite +policies are specified in does not matter.

+
+rateLimitPolicy +
+ + +RateLimitPolicy + + +
+(Optional) +

The policy for rate limiting on the route.

+
+requestRedirectPolicy +
+ + +HTTPRequestRedirectPolicy + + +
+(Optional) +

RequestRedirectPolicy defines an HTTP redirection.

+
+

Service +

+

+(Appears on: +Route, +TCPProxy) +

+

+

Service defines an Kubernetes Service to proxy traffic.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+

Name is the name of Kubernetes service to proxy traffic. +Names defined here will be used to look up corresponding endpoints which contain the ips to route.

+
+port +
+ +int + +
+

Port (defined as Integer) to proxy traffic to since a service can have multiple defined.

+
+protocol +
+ +string + +
+(Optional) +

Protocol may be used to specify (or override) the protocol used to reach this Service. +Values may be tls, h2, h2c. If omitted, protocol-selection falls back on Service annotations.

+
+weight +
+ +int64 + +
+(Optional) +

Weight defines percentage of traffic to balance traffic

+
+validation +
+ + +UpstreamValidation + + +
+(Optional) +

UpstreamValidation defines how to verify the backend service’s certificate

+
+mirror +
+ +bool + +
+

If Mirror is true the Service will receive a read only mirror of the traffic for this route.

+
+requestHeadersPolicy +
+ + +HeadersPolicy + + +
+(Optional) +

The policy for managing request headers during proxying. +Rewriting the ‘Host’ header is not supported.

+
+responseHeadersPolicy +
+ + +HeadersPolicy + + +
+(Optional) +

The policy for managing response headers during proxying. +Rewriting the ‘Host’ header is not supported.

+
+cookieRewritePolicies +
+ + +[]CookieRewritePolicy + + +
+(Optional) +

The policies for rewriting Set-Cookie header attributes.

+
+

SubCondition +

+

+(Appears on: +DetailedCondition) +

+

+

SubCondition is a Condition-like type intended for use as a subcondition inside a DetailedCondition.

+

It contains a subset of the Condition fields.

+

It is intended for warnings and errors, so type names should use abnormal-true polarity, +that is, they should be of the form “ErrorPresent: true”.

+

The expected lifecycle for these errors is that they should only be present when the error or warning is, +and should be removed when they are not relevant.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+type +
+ +string + +
+

Type of condition in CamelCase or in foo.example.com/CamelCase.

+

This must be in abnormal-true polarity, that is, ErrorFound or controller.io/ErrorFound.

+

The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)

+
+status +
+ + +Kubernetes meta/v1.ConditionStatus + + +
+

Status of the condition, one of True, False, Unknown.

+
+reason +
+ +string + +
+

Reason contains a programmatic identifier indicating the reason for the condition’s last transition. +Producers of specific condition types may define expected values and meanings for this field, +and whether the values are considered a guaranteed API.

+

The value should be a CamelCase string.

+

This field may not be empty.

+
+message +
+ +string + +
+

Message is a human readable message indicating details about the transition.

+

This may be an empty string.

+
+

TCPHealthCheckPolicy +

+

+(Appears on: +TCPProxy) +

+

+

TCPHealthCheckPolicy defines health checks on the upstream service.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+intervalSeconds +
+ +int64 + +
+(Optional) +

The interval (seconds) between health checks

+
+timeoutSeconds +
+ +int64 + +
+(Optional) +

The time to wait (seconds) for a health check response

+
+unhealthyThresholdCount +
+ +uint32 + +
+(Optional) +

The number of unhealthy health checks required before a host is marked unhealthy

+
+healthyThresholdCount +
+ +uint32 + +
+(Optional) +

The number of healthy health checks required before a host is marked healthy

+
+

TCPProxy +

+

+(Appears on: +HTTPProxySpec) +

+

+

TCPProxy contains the set of services to proxy TCP connections.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+loadBalancerPolicy +
+ + +LoadBalancerPolicy + + +
+(Optional) +

The load balancing policy for the backend services. Note that the +Cookie and RequestHash load balancing strategies cannot be used +here.

+
+services +
+ + +[]Service + + +
+(Optional) +

Services are the services to proxy traffic

+
+include +
+ + +TCPProxyInclude + + +
+(Optional) +

Include specifies that this tcpproxy should be delegated to another HTTPProxy.

+
+includes +
+ + +TCPProxyInclude + + +
+(Optional) +

IncludesDeprecated allow for specific routing configuration to be appended to another HTTPProxy in another namespace.

+

Exists due to a mistake when developing HTTPProxy and the field was marked plural +when it should have been singular. This field should stay to not break backwards compatibility to v1 users.

+
+healthCheckPolicy +
+ + +TCPHealthCheckPolicy + + +
+(Optional) +

The health check policy for this tcp proxy

+
+

TCPProxyInclude +

+

+(Appears on: +TCPProxy) +

+

+

TCPProxyInclude describes a target HTTPProxy document which contains the TCPProxy details.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+

Name of the child HTTPProxy

+
+namespace +
+ +string + +
+(Optional) +

Namespace of the HTTPProxy to include. Defaults to the current namespace if not supplied.

+
+

TLS +

+

+(Appears on: +VirtualHost) +

+

+

TLS describes tls properties. The SNI names that will be matched on +are described in the HTTPProxy’s Spec.VirtualHost.Fqdn field.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+secretName +
+ +string + +
+

SecretName is the name of a TLS secret in the current namespace. +Either SecretName or Passthrough must be specified, but not both. +If specified, the named secret must contain a matching certificate +for the virtual host’s FQDN.

+
+minimumProtocolVersion +
+ +string + +
+(Optional) +

MinimumProtocolVersion is the minimum TLS version this vhost should +negotiate. Valid options are 1.2 (default) and 1.3. Any other value +defaults to TLS 1.2.

+
+passthrough +
+ +bool + +
+(Optional) +

Passthrough defines whether the encrypted TLS handshake will be +passed through to the backing cluster. Either Passthrough or +SecretName must be specified, but not both.

+
+clientValidation +
+ + +DownstreamValidation + + +
+(Optional) +

ClientValidation defines how to verify the client certificate +when an external client establishes a TLS connection to Envoy.

+

This setting:

+
    +
  1. Enables TLS client certificate validation.
  2. +
  3. Specifies how the client certificate will be validated (i.e. +validation required or skipped).
  4. +
+

Note: Setting client certificate validation to be skipped should +be only used in conjunction with an external authorization server that +performs client validation as Contour will ensure client certificates +are passed along.

+
+enableFallbackCertificate +
+ +bool + +
+

EnableFallbackCertificate defines if the vhost should allow a default certificate to +be applied which handles all requests which don’t match the SNI defined in this vhost.

+
+

TLSCertificateDelegationSpec +

+

+(Appears on: +TLSCertificateDelegation) +

+

+

TLSCertificateDelegationSpec defines the spec of the CRD

+

+ + + + + + + + + + + + + +
FieldDescription
+delegations +
+ + +[]CertificateDelegation + + +
+
+

TLSCertificateDelegationStatus +

+

+(Appears on: +TLSCertificateDelegation) +

+

+

TLSCertificateDelegationStatus allows for the status of the delegation +to be presented to the user.

+

+ + + + + + + + + + + + + +
FieldDescription
+conditions +
+ + +[]DetailedCondition + + +
+(Optional) +

Conditions contains information about the current status of the HTTPProxy, +in an upstream-friendly container.

+

Contour will update a single condition, Valid, that is in normal-true polarity. +That is, when currentStatus is valid, the Valid condition will be status: true, +and vice versa.

+

Contour will leave untouched any other Conditions set in this block, +in case some other controller wants to add a Condition.

+

If you are another controller owner and wish to add a condition, you should +namespace your condition with a label, like controller.domain.com\ConditionName.

+
+

TimeoutPolicy +

+

+(Appears on: +Route, +ExtensionServiceSpec) +

+

+

TimeoutPolicy configures timeouts that are used for handling network requests.

+

TimeoutPolicy durations are expressed in the Go Duration format. +Valid time units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”. +The string “infinity” is also a valid input and specifies no timeout. +A value of “0s” will be treated as if the field were not set, i.e. by using Envoy’s default behavior.

+

Example input values: “300ms”, “5s”, “1m”.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+response +
+ +string + +
+(Optional) +

Timeout for receiving a response from the server after processing a request from client. +If not supplied, Envoy’s default value of 15s applies.

+
+idle +
+ +string + +
+(Optional) +

Timeout for how long the proxy should wait while there is no activity during single request/response (for HTTP/1.1) or stream (for HTTP/2). +Timeout will not trigger while HTTP/1.1 connection is idle between two consecutive requests. +If not specified, there is no per-route idle timeout, though a connection manager-wide +stream_idle_timeout default of 5m still applies.

+
+

UpstreamValidation +

+

+(Appears on: +Service, +ExtensionServiceSpec) +

+

+

UpstreamValidation defines how to verify the backend service’s certificate

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+caSecret +
+ +string + +
+

Name or namespaced name of the Kubernetes secret used to validate the certificate presented by the backend

+
+subjectName +
+ +string + +
+

Key which is expected to be present in the ‘subjectAltName’ of the presented certificate

+
+

VirtualHost +

+

+(Appears on: +HTTPProxySpec) +

+

+

VirtualHost appears at most once. If it is present, the object is considered +to be a “root”.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+fqdn +
+ +string + +
+

The fully qualified domain name of the root of the ingress tree +all leaves of the DAG rooted at this object relate to the fqdn.

+
+tls +
+ + +TLS + + +
+(Optional) +

If present the fields describes TLS properties of the virtual +host. The SNI names that will be matched on are described in fqdn, +the tls.secretName secret must contain a certificate that itself +contains a name that matches the FQDN.

+
+authorization +
+ + +AuthorizationServer + + +
+(Optional) +

This field configures an extension service to perform +authorization for this virtual host. Authorization can +only be configured on virtual hosts that have TLS enabled. +If the TLS configuration requires client certificate +validation, the client certificate is always included in the +authentication check request.

+
+corsPolicy +
+ + +CORSPolicy + + +
+(Optional) +

Specifies the cross-origin policy to apply to the VirtualHost.

+
+rateLimitPolicy +
+ + +RateLimitPolicy + + +
+(Optional) +

The policy for rate limiting on the virtual host.

+
+
+

projectcontour.io/v1alpha1

+

+

Package v1alpha1 contains API Schema definitions for the projectcontour.io v1alpha1 API group

+

+Resource Types: + +

ContourConfiguration +

+

+

ContourConfiguration is the schema for a Contour instance.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion +
+string
+ +projectcontour.io/v1alpha1 + +
+kind +
+string +
ContourConfiguration
+metadata +
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec +
+ + +ContourConfigurationSpec + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+xdsServer +
+ + +XDSServerConfig + + +
+(Optional) +

XDSServer contains parameters for the xDS server.

+
+ingress +
+ + +IngressConfig + + +
+(Optional) +

Ingress contains parameters for ingress options.

+
+debug +
+ + +DebugConfig + + +
+(Optional) +

Debug contains parameters to enable debug logging +and debug interfaces inside Contour.

+
+health +
+ + +HealthConfig + + +
+(Optional) +

Health defines the endpoints Contour uses to serve health checks.

+
+envoy +
+ + +EnvoyConfig + + +
+(Optional) +

Envoy contains parameters for Envoy as well +as how to optionally configure a managed Envoy fleet.

+
+gateway +
+ + +GatewayConfig + + +
+(Optional) +

Gateway contains parameters for the gateway-api Gateway that Contour +is configured to serve traffic.

+
+httpproxy +
+ + +HTTPProxyConfig + + +
+(Optional) +

HTTPProxy defines parameters on HTTPProxy.

+
+enableExternalNameService +
+ +bool + +
+(Optional) +

EnableExternalNameService allows processing of ExternalNameServices +Defaults to disabled for security reasons.

+
+rateLimitService +
+ + +RateLimitServiceConfig + + +
+(Optional) +

RateLimitService optionally holds properties of the Rate Limit Service +to be used for global rate limiting.

+
+policy +
+ + +PolicyConfig + + +
+(Optional) +

Policy specifies default policy applied if not overridden by the user

+
+metrics +
+ + +MetricsConfig + + +
+(Optional) +

Metrics defines the endpoint Contour uses to serve metrics.

+
+
+status +
+ + +ContourConfigurationStatus + + +
+(Optional) +
+

ContourDeployment +

+

+

ContourDeployment is the schema for a Contour Deployment.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion +
+string
+ +projectcontour.io/v1alpha1 + +
+kind +
+string +
ContourDeployment
+metadata +
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec +
+ + +ContourDeploymentSpec + + +
+
+
+ + + + + + + + + +
+replicas +
+ +int32 + +
+

Replicas is the desired number of Contour replicas. If unset, +defaults to 2.

+
+config +
+ + +ContourConfigurationSpec + + +
+

Config is the config that the instances of Contour are to utilize.

+
+
+status +
+ + +ContourDeploymentStatus + + +
+
+

ExtensionService +

+

+

ExtensionService is the schema for the Contour extension services API. +An ExtensionService resource binds a network service to the Contour +API so that Contour API features can be implemented by collaborating +components.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion +
+string
+ +projectcontour.io/v1alpha1 + +
+kind +
+string +
ExtensionService
+metadata +
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec +
+ + +ExtensionServiceSpec + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+services +
+ + +[]ExtensionServiceTarget + + +
+

Services specifies the set of Kubernetes Service resources that +receive GRPC extension API requests. +If no weights are specified for any of the entries in +this array, traffic will be spread evenly across all the +services. +Otherwise, traffic is balanced proportionally to the +Weight field in each entry.

+
+validation +
+ + +UpstreamValidation + + +
+(Optional) +

UpstreamValidation defines how to verify the backend service’s certificate

+
+protocol +
+ +string + +
+(Optional) +

Protocol may be used to specify (or override) the protocol used to reach this Service. +Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations.

+
+loadBalancerPolicy +
+ + +LoadBalancerPolicy + + +
+(Optional) +

The policy for load balancing GRPC service requests. Note that the +Cookie and RequestHash load balancing strategies cannot be used +here.

+
+timeoutPolicy +
+ + +TimeoutPolicy + + +
+(Optional) +

The timeout policy for requests to the services.

+
+protocolVersion +
+ + +ExtensionProtocolVersion + + +
+(Optional) +

This field sets the version of the GRPC protocol that Envoy uses to +send requests to the extension service. Since Contour always uses the +v3 Envoy API, this is currently fixed at “v3”. However, other +protocol options will be available in future.

+
+
+status +
+ + +ExtensionServiceStatus + + +
+
+

AccessLogFields +([]string alias)

+

+(Appears on: +EnvoyLogging) +

+

+

+

AccessLogType +(string alias)

+

+(Appears on: +EnvoyLogging) +

+

+

AccessLogType is the name of a supported access logging mechanism.

+

+

ClusterDNSFamilyType +(string alias)

+

+(Appears on: +ClusterParameters) +

+

+

ClusterDNSFamilyType is the Ip family to use for resolving DNS +names in an Envoy cluster config.

+

+

ClusterParameters +

+

+(Appears on: +EnvoyConfig) +

+

+

ClusterParameters holds various configurable cluster values.

+

+ + + + + + + + + + + + + +
FieldDescription
+dnsLookupFamily +
+ + +ClusterDNSFamilyType + + +
+

DNSLookupFamily defines how external names are looked up +When configured as V4, the DNS resolver will only perform a lookup +for addresses in the IPv4 family. If V6 is configured, the DNS resolver +will only perform a lookup for addresses in the IPv6 family. +If AUTO is configured, the DNS resolver will first perform a lookup +for addresses in the IPv6 family and fallback to a lookup for addresses +in the IPv4 family. +Note: This only applies to externalName clusters.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto.html#envoy-v3-api-enum-config-cluster-v3-cluster-dnslookupfamily +for more information.

+
+

ContourConfigurationSpec +

+

+(Appears on: +ContourConfiguration, +ContourDeploymentSpec) +

+

+

ContourConfigurationSpec represents a configuration of a Contour controller. +It contains most of all the options that can be customized, the +other remaining options being command line flags.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+xdsServer +
+ + +XDSServerConfig + + +
+(Optional) +

XDSServer contains parameters for the xDS server.

+
+ingress +
+ + +IngressConfig + + +
+(Optional) +

Ingress contains parameters for ingress options.

+
+debug +
+ + +DebugConfig + + +
+(Optional) +

Debug contains parameters to enable debug logging +and debug interfaces inside Contour.

+
+health +
+ + +HealthConfig + + +
+(Optional) +

Health defines the endpoints Contour uses to serve health checks.

+
+envoy +
+ + +EnvoyConfig + + +
+(Optional) +

Envoy contains parameters for Envoy as well +as how to optionally configure a managed Envoy fleet.

+
+gateway +
+ + +GatewayConfig + + +
+(Optional) +

Gateway contains parameters for the gateway-api Gateway that Contour +is configured to serve traffic.

+
+httpproxy +
+ + +HTTPProxyConfig + + +
+(Optional) +

HTTPProxy defines parameters on HTTPProxy.

+
+enableExternalNameService +
+ +bool + +
+(Optional) +

EnableExternalNameService allows processing of ExternalNameServices +Defaults to disabled for security reasons.

+
+rateLimitService +
+ + +RateLimitServiceConfig + + +
+(Optional) +

RateLimitService optionally holds properties of the Rate Limit Service +to be used for global rate limiting.

+
+policy +
+ + +PolicyConfig + + +
+(Optional) +

Policy specifies default policy applied if not overridden by the user

+
+metrics +
+ + +MetricsConfig + + +
+(Optional) +

Metrics defines the endpoint Contour uses to serve metrics.

+
+

ContourConfigurationStatus +

+

+(Appears on: +ContourConfiguration) +

+

+

ContourConfigurationStatus defines the observed state of a ContourConfiguration resource.

+

+ + + + + + + + + + + + + +
FieldDescription
+conditions +
+ + +[]DetailedCondition + + +
+(Optional) +

Conditions contains the current status of the Contour resource.

+

Contour will update a single condition, Valid, that is in normal-true polarity.

+

Contour will not modify any other Conditions set in this block, +in case some other controller wants to add a Condition.

+
+

ContourDeploymentSpec +

+

+(Appears on: +ContourDeployment) +

+

+

ContourDeploymentSpec defines the parameters of how a Contour +instance should be configured.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+replicas +
+ +int32 + +
+

Replicas is the desired number of Contour replicas. If unset, +defaults to 2.

+
+config +
+ + +ContourConfigurationSpec + + +
+

Config is the config that the instances of Contour are to utilize.

+
+

ContourDeploymentStatus +

+

+(Appears on: +ContourDeployment) +

+

+

ContourDeploymentStatus defines the observed state of a ContourDeployment resource.

+

+ + + + + + + + + + + + + +
FieldDescription
+conditions +
+ + +[]DetailedCondition + + +
+(Optional) +

Conditions contains the current status of the Contour resource.

+

Contour will update a single condition, Valid, that is in normal-true polarity.

+

Contour will not modify any other Conditions set in this block, +in case some other controller wants to add a Condition.

+
+

DebugConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

DebugConfig contains Contour specific troubleshooting options.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+address +
+ +string + +
+(Optional) +

Defines the Contour debug address interface.

+
+port +
+ +int + +
+(Optional) +

Defines the Contour debug address port.

+
+logLevel +
+ + +LogLevel + + +
+

DebugLogLevel defines the log level which Contour will +use when outputting log information.

+
+kubernetesLogLevel +
+ +uint + +
+(Optional) +

KubernetesDebugLogLevel defines the log level which Contour will +use when outputting Kubernetes specific log information.

+

Details: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md

+
+

EnvoyConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

EnvoyConfig defines how Envoy is to be Configured from Contour.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+listener +
+ + +EnvoyListenerConfig + + +
+

Listener hold various configurable Envoy listener values.

+
+service +
+ + +NamespacedName + + +
+

Service holds Envoy service parameters for setting Ingress status.

+
+http +
+ + +EnvoyListener + + +
+

Defines the HTTP Listener for Envoy.

+
+https +
+ + +EnvoyListener + + +
+

Defines the HTTP Listener for Envoy.

+
+health +
+ + +HealthConfig + + +
+(Optional) +

Health defines the endpoint Envoy uses to serve health checks.

+
+metrics +
+ + +MetricsConfig + + +
+

Metrics defines the endpoint Envoy uses to serve metrics.

+
+clientCertificate +
+ + +NamespacedName + + +
+(Optional) +

ClientCertificate defines the namespace/name of the Kubernetes +secret containing the client certificate and private key +to be used when establishing TLS connection to upstream +cluster.

+
+logging +
+ + +EnvoyLogging + + +
+

Logging defines how Envoy’s logs can be configured.

+
+defaultHTTPVersions +
+ + +[]HTTPVersionType + + +
+

DefaultHTTPVersions defines the default set of HTTPS +versions the proxy should accept. HTTP versions are +strings of the form “HTTP/xx”. Supported versions are +“HTTP/1.1” and “HTTP/2”.

+
+timeouts +
+ + +TimeoutParameters + + +
+(Optional) +

Timeouts holds various configurable timeouts that can +be set in the config file.

+
+cluster +
+ + +ClusterParameters + + +
+

Cluster holds various configurable Envoy cluster values that can +be set in the config file.

+
+network +
+ + +NetworkParameters + + +
+

Network holds various configurable Envoy network values.

+
+

EnvoyListener +

+

+(Appears on: +EnvoyConfig) +

+

+

EnvoyListener defines parameters for an Envoy Listener.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+address +
+ +string + +
+

Defines an Envoy Listener Address.

+
+port +
+ +int + +
+

Defines an Envoy listener Port.

+
+accessLog +
+ +string + +
+

AccessLog defines where Envoy logs are outputted for this listener.

+
+

EnvoyListenerConfig +

+

+(Appears on: +EnvoyConfig) +

+

+

EnvoyListenerConfig hold various configurable Envoy listener values.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+useProxyProtocol +
+ +bool + +
+

Use PROXY protocol for all listeners.

+
+disableAllowChunkedLength +
+ +bool + +
+

DisableAllowChunkedLength disables the RFC-compliant Envoy behavior to +strip the “Content-Length” header if “Transfer-Encoding: chunked” is +also set. This is an emergency off-switch to revert back to Envoy’s +default behavior in case of failures. Please file an issue if failures +are encountered. +See: https://github.com/projectcontour/contour/issues/3221

+
+connectionBalancer +
+ +string + +
+

ConnectionBalancer. If the value is exact, the listener will use the exact connection balancer +See https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/listener.proto#envoy-api-msg-listener-connectionbalanceconfig +for more information.

+
+tls +
+ + +EnvoyTLS + + +
+

TLS holds various configurable Envoy TLS listener values.

+
+

EnvoyLogging +

+

+(Appears on: +EnvoyConfig) +

+

+

EnvoyLogging defines how Envoy’s logs can be configured.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+accessLogFormat +
+ + +AccessLogType + + +
+

AccessLogFormat sets the global access log format. +Valid options are ‘envoy’ or ‘json’

+
+accessLogFormatString +
+ +string + +
+(Optional) +

AccessLogFormatString sets the access log format when format is set to envoy. +When empty, Envoy’s default format is used.

+
+jsonFields +
+ + +AccessLogFields + + +
+(Optional) +

AccessLogFields sets the fields that JSON logging will +output when AccessLogFormat is json.

+
+

EnvoyTLS +

+

+(Appears on: +EnvoyListenerConfig) +

+

+

EnvoyTLS describes tls parameters for Envoy listneners.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+minimumProtocolVersion +
+ +string + +
+

MinimumProtocolVersion is the minimum TLS version this vhost should +negotiate. Valid options are 1.2 (default) and 1.3.

+
+cipherSuites +
+ + +[]TLSCipherType + + +
+

CipherSuites defines the TLS ciphers to be supported by Envoy TLS +listeners when negotiating TLS 1.2. Ciphers are validated against the +set that Envoy supports by default. This parameter should only be used +by advanced users. Note that these will be ignored when TLS 1.3 is in +use.

+

See: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/common.proto#extensions-transport-sockets-tls-v3-tlsparameters +Note: This list is a superset of what is valid for stock Envoy builds and those using BoringSSL FIPS.

+
+

ExtensionProtocolVersion +(string alias)

+

+(Appears on: +ExtensionServiceSpec) +

+

+

ExtensionProtocolVersion is the version of the GRPC protocol used +to access extension services. The only version currently supported +is “v3”.

+

+

ExtensionServiceSpec +

+

+(Appears on: +ExtensionService) +

+

+

ExtensionServiceSpec defines the desired state of an ExtensionService resource.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+services +
+ + +[]ExtensionServiceTarget + + +
+

Services specifies the set of Kubernetes Service resources that +receive GRPC extension API requests. +If no weights are specified for any of the entries in +this array, traffic will be spread evenly across all the +services. +Otherwise, traffic is balanced proportionally to the +Weight field in each entry.

+
+validation +
+ + +UpstreamValidation + + +
+(Optional) +

UpstreamValidation defines how to verify the backend service’s certificate

+
+protocol +
+ +string + +
+(Optional) +

Protocol may be used to specify (or override) the protocol used to reach this Service. +Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations.

+
+loadBalancerPolicy +
+ + +LoadBalancerPolicy + + +
+(Optional) +

The policy for load balancing GRPC service requests. Note that the +Cookie and RequestHash load balancing strategies cannot be used +here.

+
+timeoutPolicy +
+ + +TimeoutPolicy + + +
+(Optional) +

The timeout policy for requests to the services.

+
+protocolVersion +
+ + +ExtensionProtocolVersion + + +
+(Optional) +

This field sets the version of the GRPC protocol that Envoy uses to +send requests to the extension service. Since Contour always uses the +v3 Envoy API, this is currently fixed at “v3”. However, other +protocol options will be available in future.

+
+

ExtensionServiceStatus +

+

+(Appears on: +ExtensionService) +

+

+

ExtensionServiceStatus defines the observed state of an +ExtensionService resource.

+

+ + + + + + + + + + + + + +
FieldDescription
+conditions +
+ + +[]DetailedCondition + + +
+(Optional) +

Conditions contains the current status of the ExtensionService resource.

+

Contour will update a single condition, Valid, that is in normal-true polarity.

+

Contour will not modify any other Conditions set in this block, +in case some other controller wants to add a Condition.

+
+

ExtensionServiceTarget +

+

+(Appears on: +ExtensionServiceSpec) +

+

+

ExtensionServiceTarget defines an Kubernetes Service to target with +extension service traffic.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+

Name is the name of Kubernetes service that will accept service +traffic.

+
+port +
+ +int + +
+

Port (defined as Integer) to proxy traffic to since a service can have multiple defined.

+
+weight +
+ +uint32 + +
+(Optional) +

Weight defines proportion of traffic to balance to the Kubernetes Service.

+
+

GatewayConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

GatewayConfig holds the config for Gateway API controllers.

+

+ + + + + + + + + + + + + +
FieldDescription
+controllerName +
+ +string + +
+

ControllerName is used to determine whether Contour should reconcile a +GatewayClass. The string takes the form of “projectcontour.io//contour”. +If unset, the gatewayclass controller will not be started.

+
+

HTTPProxyConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

HTTPProxyConfig defines parameters on HTTPProxy.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+disablePermitInsecure +
+ +bool + +
+

DisablePermitInsecure disables the use of the +permitInsecure field in HTTPProxy.

+
+rootNamespaces +
+ +[]string + +
+(Optional) +

Restrict Contour to searching these namespaces for root ingress routes.

+
+fallbackCertificate +
+ + +NamespacedName + + +
+(Optional) +

FallbackCertificate defines the namespace/name of the Kubernetes secret to +use as fallback when a non-SNI request is received.

+
+

HTTPVersionType +(string alias)

+

+(Appears on: +EnvoyConfig) +

+

+

HTTPVersionType is the name of a supported HTTP version.

+

+

HeadersPolicy +

+

+(Appears on: +PolicyConfig) +

+

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+set +
+ +map[string]string + +
+(Optional) +
+remove +
+ +[]string + +
+(Optional) +
+

HealthConfig +

+

+(Appears on: +ContourConfigurationSpec, +EnvoyConfig) +

+

+

HealthConfig defines the endpoints to enable health checks.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+address +
+ +string + +
+

Defines the health address interface.

+
+port +
+ +int + +
+

Defines the health port.

+
+

IngressConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

IngressConfig defines ingress specific config items.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+className +
+ +string + +
+(Optional) +

Ingress Class Name Contour should use.

+
+statusAddress +
+ +string + +
+(Optional) +

Address to set in Ingress object status.

+
+

LogLevel +(string alias)

+

+(Appears on: +DebugConfig) +

+

+

LogLevel is the logging levels available.

+

+

MetricsConfig +

+

+(Appears on: +ContourConfigurationSpec, +EnvoyConfig) +

+

+

MetricsConfig defines the metrics endpoint.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+address +
+ +string + +
+

Defines the metrics address interface.

+
+port +
+ +int + +
+

Defines the metrics port.

+
+tls +
+ + +MetricsTLS + + +
+(Optional) +

TLS holds TLS file config details. +Metrics and health endpoints cannot have same port number when metrics is served over HTTPS.

+
+

MetricsTLS +

+

+(Appears on: +MetricsConfig) +

+

+

TLS holds TLS file config details.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+caFile +
+ +string + +
+(Optional) +

CA filename.

+
+certFile +
+ +string + +
+(Optional) +

Client certificate filename.

+
+keyFile +
+ +string + +
+(Optional) +

Client key filename.

+
+

NamespacedName +

+

+(Appears on: +EnvoyConfig, +HTTPProxyConfig, +RateLimitServiceConfig) +

+

+

NamespacedName defines the namespace/name of the Kubernetes resource referred from the config file. +Used for Contour config YAML file parsing, otherwise we could use K8s types.NamespacedName.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+name +
+ +string + +
+
+namespace +
+ +string + +
+
+

NetworkParameters +

+

+(Appears on: +EnvoyConfig) +

+

+

NetworkParameters hold various configurable network values.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+numTrustedHops +
+ +uint32 + +
+(Optional) +

XffNumTrustedHops defines the number of additional ingress proxy hops from the +right side of the x-forwarded-for HTTP header to trust when determining the origin +client’s IP address.

+

See https://www.envoyproxy.io/docs/envoy/v1.17.0/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto?highlight=xff_num_trusted_hops +for more information.

+
+adminPort +
+ +int + +
+

Configure the port used to access the Envoy Admin interface. +If configured to port “0” then the admin interface is disabled.

+
+

PolicyConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

PolicyConfig holds default policy used if not explicitly set by the user

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+requestHeaders +
+ + +HeadersPolicy + + +
+(Optional) +

RequestHeadersPolicy defines the request headers set/removed on all routes

+
+responseHeaders +
+ + +HeadersPolicy + + +
+(Optional) +

ResponseHeadersPolicy defines the response headers set/removed on all routes

+
+applyToIngress +
+ +bool + +
+(Optional) +

ApplyToIngress determines if the Policies will apply to ingress objects

+
+

RateLimitServiceConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

RateLimitServiceConfig defines properties of a global Rate Limit Service.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+extensionService +
+ + +NamespacedName + + +
+

ExtensionService identifies the extension service defining the RLS.

+
+domain +
+ +string + +
+

Domain is passed to the Rate Limit Service.

+
+failOpen +
+ +bool + +
+

FailOpen defines whether to allow requests to proceed when the +Rate Limit Service fails to respond with a valid rate limit +decision within the timeout defined on the extension service.

+
+enableXRateLimitHeaders +
+ +bool + +
+

EnableXRateLimitHeaders defines whether to include the X-RateLimit +headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset +(as defined by the IETF Internet-Draft linked below), on responses +to clients when the Rate Limit Service is consulted for a request.

+

ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html

+
+

TLS +

+

+(Appears on: +XDSServerConfig) +

+

+

TLS holds TLS file config details.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+caFile +
+ +string + +
+(Optional) +

CA filename.

+
+certFile +
+ +string + +
+(Optional) +

Client certificate filename.

+
+keyFile +
+ +string + +
+(Optional) +

Client key filename.

+
+insecure +
+ +bool + +
+

Allow serving the xDS gRPC API without TLS.

+
+

TLSCipherType +(string alias)

+

+(Appears on: +EnvoyTLS) +

+

+

+

TimeoutParameters +

+

+(Appears on: +EnvoyConfig) +

+

+

TimeoutParameters holds various configurable proxy timeout values.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+requestTimeout +
+ +string + +
+(Optional) +

RequestTimeout sets the client request timeout globally for Contour. Note that +this is a timeout for the entire request, not an idle timeout. Omit or set to +“infinity” to disable the timeout entirely.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-request-timeout +for more information.

+
+connectionIdleTimeout +
+ +string + +
+(Optional) +

ConnectionIdleTimeout defines how long the proxy should wait while there are +no active requests (for HTTP/1.1) or streams (for HTTP/2) before terminating +an HTTP connection. Set to “infinity” to disable the timeout entirely.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-httpprotocoloptions-idle-timeout +for more information.

+
+streamIdleTimeout +
+ +string + +
+(Optional) +

StreamIdleTimeout defines how long the proxy should wait while there is no +request activity (for HTTP/1.1) or stream activity (for HTTP/2) before +terminating the HTTP request or stream. Set to “infinity” to disable the +timeout entirely.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-stream-idle-timeout +for more information.

+
+maxConnectionDuration +
+ +string + +
+(Optional) +

MaxConnectionDuration defines the maximum period of time after an HTTP connection +has been established from the client to the proxy before it is closed by the proxy, +regardless of whether there has been activity or not. Omit or set to “infinity” for +no max duration.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-httpprotocoloptions-max-connection-duration +for more information.

+
+delayedCloseTimeout +
+ +string + +
+(Optional) +

DelayedCloseTimeout defines how long envoy will wait, once connection +close processing has been initiated, for the downstream peer to close +the connection before Envoy closes the socket associated with the connection.

+

Setting this timeout to ‘infinity’ will disable it, equivalent to setting it to ‘0’ +in Envoy. Leaving it unset will result in the Envoy default value being used.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-delayed-close-timeout +for more information.

+
+connectionShutdownGracePeriod +
+ +string + +
+(Optional) +

ConnectionShutdownGracePeriod defines how long the proxy will wait between sending an +initial GOAWAY frame and a second, final GOAWAY frame when terminating an HTTP/2 connection. +During this grace period, the proxy will continue to respond to new streams. After the final +GOAWAY frame has been sent, the proxy will refuse new streams.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-drain-timeout +for more information.

+
+

XDSServerConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

XDSServerConfig holds the config for the Contour xDS server.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+type +
+ + +XDSServerType + + +
+

Defines the XDSServer to use for contour serve.

+
+address +
+ +string + +
+

Defines the xDS gRPC API address which Contour will serve.

+
+port +
+ +int + +
+

Defines the xDS gRPC API port which Contour will serve.

+
+tls +
+ + +TLS + + +
+(Optional) +

TLS holds TLS file config details.

+
+

XDSServerType +(string alias)

+

+(Appears on: +XDSServerConfig) +

+

+

XDSServerType is the type of xDS server implementation.

+

+
+

+Generated with gen-crd-api-reference-docs. +

diff --git a/site/content/docs/v1.20.0/config/api.md b/site/content/docs/v1.20.0/config/api.md new file mode 100644 index 00000000000..99809537d11 --- /dev/null +++ b/site/content/docs/v1.20.0/config/api.md @@ -0,0 +1,3 @@ +# Contour API Reference + +{{% include-html api-reference.html %}} diff --git a/site/content/docs/v1.20.0/config/client-authorization.md b/site/content/docs/v1.20.0/config/client-authorization.md new file mode 100644 index 00000000000..43de318f425 --- /dev/null +++ b/site/content/docs/v1.20.0/config/client-authorization.md @@ -0,0 +1,123 @@ +# Client Authorization + +Contour supports integrating external servers to authorize client requests. + +Envoy implements external authorization in the [ext_authz][1] filter. +This filter intercepts client requests and holds them while it sends a check +request to an external server. +The filter uses the check result to either allow the request to proceed, or to +deny or redirect the request. + +The diagram below shows the sequence of requests involved in the successful +authorization of a HTTP request: + +

+client authorization sequence diagram +

+ +The [external authorization][7] guides demonstrates how to deploy HTTP basic +authentication using Contour and [contour-authserver](https://github.com/projectcontour/contour-authserver). + +## Extension Services + +The starting point for external authorization in Contour is the +[ExtensionService][2] API. +This API creates a cluster which Envoy can use to send requests to an external server. +In principle, the Envoy cluster can be used for any purpose, but in this +document we are concerned only with how to use it as an authorization service. + +An authorization service is a gRPC service that implements the Envoy [CheckRequest][3] protocol. +Note that Contour requires the extension to implement the "v3" version of the protocol. +Contour is compatible with any authorization server that implements this protocol. + +The primary field of interest in the `ExtensionService` CRD is the +`.spec.services` field. +This field lists the Kubernetes Services that will receive the check requests. +The `.spec.services[].name` field contains the name of the Service, which must +exist in the same namespace as the `ExtensionService` object. +The `ExtensionService` object must exist in the same namespace as the +Services they target to ensure that both objects are under the same +administrative control. + +### Load Balancing for Extension Services + +An `ExtensionService` can be configured to send traffic to multiple Kubernetes Services. +In this case, requests are divided proportionally across the Services according +to the weight in the `.spec.services[].weight` field. +The service weight can be used to flexibly shift traffic between Services for +reasons like implementing blue-green deployments. +The `.spec.loadBalancerPolicy` field configures how Envoy will load balance +requests to the endpoints within each Service. + +### TLS Validation for Extension Services + +Since authorizing a client request may involve passing sensitive credentials +from a HTTP request to the authorization service, the connection to the +authorization server should be as secure as possible. +Contour defaults the `.spec.protocol` field to "h2", which configures +Envoy to use HTTP/2 over TLS for the authorization service connection. + +The [.spec.validation][4] field configures how Envoy should verify the TLS +identity of the authorization server. +This is a critical protection against accidentally sending credentials to an +imposter service and should be enabled for all production deployments. +The `.spec.validation` field should specify the expected server name +from the authorization server's TLS certificate, and the trusted CA bundle +that can be used to validate the TLS chain of trust. + +## Authorizing Virtual Hosts + +The [.spec.virtualhost.authorization][5] field in the Contour `HTTPProxy` +API connects a virtual host to an authorization server that is bound by an +`ExtensionService` object. +Each virtual host can use a different `ExtensionService`, but only one +`ExtensionService` can be used by a single virtual host. +Authorization servers can only be attached to `HTTPProxy` objects that have TLS +termination enabled. + +### Migrating from Application Authorization + +When applications perform their own authorization, migrating to centralized +authorization may need some planning. +The `.spec.virtualhost.authorization.failOpen` field controls how client +requests should be handled when the authorization server fails. +During a migration process, this can be set to `true`, so that if the +authorization server becomes unavailable, clients can gracefully fall back to +the existing application authorization mechanism. + +### Scoping Authorization Policy Settings + +It is common for services to contain some HTTP request paths that require +authorization and some that do not. +The HTTPProxy [authorization policy][6] allows authorization to be +disabled for both an entire virtual host and for specific routes. + +The initial authorization policy is set on the HTTPProxy virtual host +in the `.spec.virtualhost.authorization.authPolicy` field. +This configures whether authorization is enabled, and the default authorization policy context. +If authorization is disabled on the virtual host, it is also disabled by +default on all the routes for that virtual host that do not specify an authorization policy. +However, a route can configure its own authorization policy (in the +`.spec.routes[].authPolicy` field) that can configure whether authorization +is enabled, irrespective of the virtual host setting. + +The authorization policy context is a way to configure a set of key/value +pairs that will be sent to the authorization server with each request check +request. +The keys and values that should be specified here depend on which authorization +server has been configured. +This facility is intended for configuring authorization-specific information, such as +the basic authentication realm, or OIDC parameters. + +The initial context map can be set on the virtual host. +This sets the context keys that will be sent on every check request. +A route can overwrite the value for a context key by setting it in the +context field of authorization policy for the route. + +[1]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter +[2]: api/#projectcontour.io/v1alpha1.ExtensionService +[3]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto +[4]: api/#projectcontour.io/v1.UpstreamValidation +[5]: api/#projectcontour.io/v1.AuthorizationServer +[6]: api/#projectcontour.io/v1.AuthorizationPolicy +[7]: /guides/external-authorization.md diff --git a/site/content/docs/v1.20.0/config/cookie-rewriting.md b/site/content/docs/v1.20.0/config/cookie-rewriting.md new file mode 100644 index 00000000000..480fc34125c --- /dev/null +++ b/site/content/docs/v1.20.0/config/cookie-rewriting.md @@ -0,0 +1,109 @@ +# Cookie Rewriting + +Contour now enables users to customize attributes on HTTP `Set-Cookie` response headers. +Application specific cookies and cookies generated by Contour's ["cookie" load balancing strategy](https://projectcontour.io/docs/v1.19.0/config/request-routing/#session-affinity) can be rewritten either per HTTPProxy `Route` or `Service`. +Users can choose to rewrite the `Path`, `Domain`, `Secure`, and `SameSite` attributes of the `Set-Cookie` header currently. +These attributes may be things an application may not be able to accurately set, without prior knowledge of how the application is deployed. +For example, if Contour is in use to rewrite the path or hostname of a request before it reaches an application backend, the application may not be able to accurately set the `Path` and `Domain` attributes in a `Set-Cookie` response header. +This feature can be used to apply security settings to ensure browsers treat generated cookies appropriately. +The `SameSite` and `Secure` attributes are currently not set by Envoy when it generates the `X-Contour-Session-Affinity`, but with this feature, users can customize this cookie further. + +## Per-Route Cookie Rewriting + +In order to implement separate cookie rewriting policies per-route, we can configure an HTTPProxy as below: + +```yaml +# cookie-rewrite-route.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: cookie-rewrite-route +spec: + virtualhost: + fqdn: cookie-rewrite-route.com + routes: + - conditions: + - prefix: /admin + services: + - name: admin-app + port: 80 + cookieRewritePolicies: + - name: X-Admin-Session + pathRewrite: + value: /admin + - conditions: + - prefix: /payments + services: + - name: payment-app + port: 80 + cookieRewritePolicies: + - name: X-User-Session + pathRewrite: + value: /payments + sameSite: Lax + - name: X-User-Data + sameSite: Lax +``` + +This HTTPProxy allows us to rewrite the `Path` attribute of the `X-Admin-Session` cookie on the `/admin` route. +In addition on the `/payments` route we rewrite the `Path` and `SameSite` attributes of the `X-User-Session` cookie and the `SameSite` attribute of the additional `X-User-Data` cookie. +If the backing services `payment-app` and `admin-app` return the specified cookies in `Set-Cookie` response headers, they will be rewritten with the values specified above. + +## Per-Service Cookie Rewriting + +Similar to the above, if we have more than one `Service` configured per `Route` but want to customize cookies separately between them we can: + +```yaml +# cookie-rewrite-service.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: cookie-rewrite-service +spec: + virtualhost: + fqdn: cookie-rewrite-service.com + routes: + - conditions: + - prefix: / + services: + - name: backend-1 + port: 80 + cookieRewritePolicies: + - name: X-User-Data-1 + domainRewrite: + value: cookie-rewrite-service.com + - name: backend-2 + port: 80 + cookieRewritePolicies: + - name: X-User-Data-2 + domainRewrite: + value: cookie-rewrite-service.com +``` + +## Rewriting Contour Session Affinity Cookie + +As mentioned above, users can use Contour's cookie load balancing strategy to enable session affinity. +Envoy generates a pretty bare-bones cookie but Contour's cookie rewriting feature can be used to customize this cookie to add security attributes: + +```yaml +# cookie-rewrite-session-affinity.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: cookie-rewrite-session-affinity +spec: + virtualhost: + fqdn: cookie-rewrite-session-affinity.com + routes: + - conditions: + - prefix: / + services: + - name: backend + port: 80 + loadBalancerPolicy: + strategy: Cookie + cookieRewritePolicies: + - name: X-Contour-Session-Affinity + sameSite: Strict + secure: true +``` diff --git a/site/content/docs/v1.20.0/config/cors.md b/site/content/docs/v1.20.0/config/cors.md new file mode 100644 index 00000000000..23e02ffc93a --- /dev/null +++ b/site/content/docs/v1.20.0/config/cors.md @@ -0,0 +1,74 @@ +# CORS + +A CORS (Cross-origin resource sharing) policy can be set for a HTTPProxy in order to allow cross-domain requests for trusted sources. +If a policy is set, it will be applied to all the routes of the virtual host. + +Contour allows configuring the headers involved in cross-domain requests. +In this example, cross-domain requests will be allowed for any domain (note the `*` value). + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: cors-example +spec: + virtualhost: + fqdn: www.example.com + corsPolicy: + allowCredentials: true + allowOrigin: + - "*" # allows any origin + allowMethods: + - GET + - POST + - OPTIONS + allowHeaders: + - authorization + - cache-control + exposeHeaders: + - Content-Length + - Content-Range + maxAge: "10m" # preflight requests can be cached for 10 minutes. + routes: + - conditions: + - prefix: / + services: + - name: cors-example + port: 80 +``` + +In the following example, cross-domain requests are restricted to `https://client.example.com` only. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: cors-example +spec: + virtualhost: + fqdn: www.example.com + corsPolicy: + allowCredentials: true + allowOrigin: + - "https://client.example.com" + allowMethods: + - GET + - POST + - OPTIONS + allowHeaders: + - authorization + - cache-control + exposeHeaders: + - Content-Length + - Content-Range + maxAge: "10m" + routes: + - conditions: + - prefix: / + services: + - name: cors-example + port: 80 +``` + +`MaxAge` durations are expressed in the Go [duration format](https://godoc.org/time#ParseDuration). +Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Only positive values are allowed and 0 disables the cache requiring a preflight `OPTIONS` check for all cross-origin requests. diff --git a/site/content/docs/v1.20.0/config/external-service-routing.md b/site/content/docs/v1.20.0/config/external-service-routing.md new file mode 100644 index 00000000000..7da431dd06b --- /dev/null +++ b/site/content/docs/v1.20.0/config/external-service-routing.md @@ -0,0 +1,47 @@ +# External Service Routing + +HTTPProxy supports routing traffic to `ExternalName` service types, but this is disabled by default, as it can lead +to inadvertent exposure of the Envoy Admin UI, allowing remote shutdown and restart of Envoy. +Please see [this security advisory](https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc) for all the details. +It can also be used to expose services in namespaces a user does not have access to, using an ExternalName of `service.namespace.svc.cluster.local`. +Please see [this Kubernetes security advisory](https://github.com/kubernetes/kubernetes/issues/103675) for more details. + +We do *not* recommend enabling ExternalName Services without a strong use case, and understanding of the security implications. + +However, To enable ExternalName processing, you must set the `enableExternalNameService` configuration file setting to `true`. +This will allow the following configuration to be valid. + +## ExternalName Support + +Contour looks at the `spec.externalName` field of the service and configures the route to use that DNS name instead of utilizing EDS. + +Note that hostnames of `localhost` or some other synonyms will be rejected (because of the aforementioned security issues). + +There's nothing specific in the HTTPProxy object that needs to be configured other than referencing a service of type `ExternalName`. +HTTPProxy supports the `requestHeadersPolicy` field to rewrite the `Host` header after first handling a request and before proxying to an upstream service. +This field can be used to ensure that the forwarded HTTP request contains the hostname that the external resource is expecting. + +_**Note:** The ports are required to be specified._ + +```yaml +# httpproxy-externalname.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + run: externaldns + name: externaldns + namespace: default +spec: + externalName: foo-basic.bar.com + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 80 + type: ExternalName +``` + +To proxy to another resource outside the cluster (e.g. A hosted object store bucket for example), configure that external resource in a service type `externalName`. +Then define a `requestHeadersPolicy` which replaces the `Host` header with the value of the external name service defined previously. +Finally, if the upstream service is served over TLS, set the `protocol` field on the service to `tls` or annotate the external name service with: `projectcontour.io/upstream-protocol.tls: 443,https`, assuming your service had a port 443 and name `https`. diff --git a/site/content/docs/v1.20.0/config/fundamentals.md b/site/content/docs/v1.20.0/config/fundamentals.md new file mode 100644 index 00000000000..a8a5785cdad --- /dev/null +++ b/site/content/docs/v1.20.0/config/fundamentals.md @@ -0,0 +1,196 @@ +# HTTPProxy Fundamentals + +The [Ingress][1] object was added to Kubernetes in version 1.1 to describe properties of a cluster-wide reverse HTTP proxy. +Since that time, the Ingress API has remained relatively unchanged, and the need to express implementation-specific capabilities has inspired an [explosion of annotations][2]. + +The goal of the HTTPProxy Custom Resource Definition (CRD) is to expand upon the functionality of the Ingress API to allow for a richer user experience as well addressing the limitations of the latter's use in multi tenant environments. + +## Key HTTPProxy Benefits + +- Safely supports multi-team Kubernetes clusters, with the ability to limit which Namespaces may configure virtual hosts and TLS credentials. +- Enables including of routing configuration for a path or domain from another HTTPProxy, possibly in another Namespace. +- Accepts multiple services within a single route and load balances traffic across them. +- Natively allows defining service weighting and load balancing strategy without annotations. +- Validation of HTTPProxy objects at creation time and status reporting for post-creation validity. + +## Ingress to HTTPProxy + +A minimal Ingress object might look like: + +```yaml +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: basic +spec: + rules: + - host: foo-basic.bar.com + http: + paths: + - backend: + service: + name: s1 + port: + number: 80 +``` + +This Ingress object, named `basic`, will route incoming HTTP traffic with a `Host:` header for `foo-basic.bar.com` to a Service named `s1` on port `80`. +Implementing similar behavior using an HTTPProxy looks like this: + +```yaml +# httpproxy.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: basic +spec: + virtualhost: + fqdn: foo-basic.bar.com + routes: + - conditions: + - prefix: / + services: + - name: s1 + port: 80 +``` + +**Lines 1-5**: As with all other Kubernetes objects, an HTTPProxy needs apiVersion, kind, and metadata fields. + +**Lines 7-8**: The presence of the `virtualhost` field indicates that this is a root HTTPProxy that is the top level entry point for this domain. + + +## Interacting with HTTPProxies + +As with all Kubernetes objects, you can use `kubectl` to create, list, describe, edit, and delete HTTPProxy CRDs. + +Creating an HTTPProxy: + +```bash +$ kubectl create -f basic.httpproxy.yaml +httpproxy "basic" created +``` + +Listing HTTPProxies: + +```bash +$ kubectl get httpproxy +NAME AGE +basic 24s +``` + +Describing HTTPProxy: + +```bash +$ kubectl describe httpproxy basic +Name: basic +Namespace: default +Labels: +API Version: projectcontour.io/v1 +Kind: HTTPProxy +Metadata: + Cluster Name: + Creation Timestamp: 2019-07-05T19:26:54Z + Resource Version: 19373717 + Self Link: /apis/projectcontour.io/v1/namespaces/default/httpproxy/basic + UID: 6036a9d7-8089-11e8-ab00-f80f4182762e +Spec: + Routes: + Conditions: + Prefix: / + Services: + Name: s1 + Port: 80 + Virtualhost: + Fqdn: foo-basic.bar.com +Events: +``` + +Deleting HTTPProxies: + +```bash +$ kubectl delete httpproxy basic +httpproxy "basic" deleted +``` + +## Status Reporting + +There are many misconfigurations that could cause an HTTPProxy or delegation to be invalid. +Contour will make its best effort to process even partially valid configuration and allow traffic to be served for the valid parts. +To aid users in resolving any issues, Contour updates a `status` field in all HTTPProxy objects. + +If an HTTPProxy object is valid, it will have a status property that looks like this: + +```yaml +status: + currentStatus: valid + description: valid HTTPProxy +``` + +If the HTTPProxy is invalid, the `currentStatus` field will be `invalid` and the `description` field will provide a description of the issue. + +As an example, if an HTTPProxy object has specified a negative value for weighting, the HTTPProxy status will be: + +```yaml +status: + currentStatus: invalid + description: "route '/foo': service 'home': weight must be greater than or equal to zero" +``` + +Some examples of invalid configurations that Contour provides statuses for: + +- Negative weight provided in the route definition. +- Invalid port number provided for service. +- Prefix in parent does not match route in delegated route. +- Root HTTPProxy created in a namespace other than the allowed root namespaces. +- A given Route of an HTTPProxy both delegates to another HTTPProxy and has a list of services. +- Orphaned route. +- Delegation chain produces a cycle. +- Root HTTPProxy does not specify fqdn. +- Multiple prefixes cannot be specified on the same set of route conditions. +- Multiple header conditions of type "exact match" with the same header key. +- Contradictory header conditions on a route, e.g. a "contains" and "notcontains" condition for the same header and value. + +Invalid configuration is ignored and will be not used in the ingress routing configuration. +Envoy will respond with an error when HTTP request is received on route with invalid configuration on following cases: + +* `502 Bad Gateway` response is sent when HTTPProxy has an include that refers to an HTTPProxy that does not exist. +* `503 Service Unavailable` response is sent when HTTPProxy refers to a service that does not exist. + +### Example + +Following example has two routes: the first one is valid, the second one refers to a service that does not exist. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: multiple-routes-with-a-missing-service +spec: + virtualhost: + fqdn: www.example.com + routes: + - conditions: + - prefix: / + services: + - name: valid-service + port: 80 + - conditions: + - prefix: /subpage + services: + - name: service-that-does-not-exist + port: 80 +``` + +The `HTTPProxy` will have condition `Valid=false` with detailed error message: `Spec.Routes unresolved service reference: service "default/service-that-does-not-exist" not found`. +Requests received for `http://www.example.com/` will be forwarded to `valid-service` but requests received for `http://www.example.com/subpage` will result in error `503 Service Unavailable` response from Envoy. + +## HTTPProxy API Specification + +The full HTTPProxy specification is described in detail in the [API documentation][4]. +There are a number of working examples of HTTPProxy objects in the [`examples/example-workload`][3] directory of the Contour Github repository. + + [1]: https://kubernetes.io/docs/concepts/services-networking/ingress/ + [2]: https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md + [3]: {{< param github_url>}}/tree/{{< param version >}}/examples/example-workload/httpproxy + [4]: api.md diff --git a/site/content/docs/v1.20.0/config/health-checks.md b/site/content/docs/v1.20.0/config/health-checks.md new file mode 100644 index 00000000000..6e0b6469908 --- /dev/null +++ b/site/content/docs/v1.20.0/config/health-checks.md @@ -0,0 +1,83 @@ +# Upstream Health Checks + +## HTTP Proxy Health Checking + +Active health checking can be configured on a per route basis. +Contour supports HTTP health checking and can be configured with various settings to tune the behavior. + +During HTTP health checking Envoy will send an HTTP request to the upstream Endpoints. +It expects a 200 response if the host is healthy. +The upstream host can return 503 if it wants to immediately notify Envoy to no longer forward traffic to it. +It is important to note that these are health checks which Envoy implements and are separate from any other system such as those that exist in Kubernetes. + +```yaml +# httpproxy-health-checks.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: health-check + namespace: default +spec: + virtualhost: + fqdn: health.bar.com + routes: + - conditions: + - prefix: / + healthCheckPolicy: + path: /healthy + intervalSeconds: 5 + timeoutSeconds: 2 + unhealthyThresholdCount: 3 + healthyThresholdCount: 5 + services: + - name: s1-health + port: 80 + - name: s2-health + port: 80 +``` + +Health check configuration parameters: + +- `path`: HTTP endpoint used to perform health checks on upstream service (e.g. `/healthz`). It expects a 200 response if the host is healthy. The upstream host can return 503 if it wants to immediately notify downstream hosts to no longer forward traffic to it. +- `host`: The value of the host header in the HTTP health check request. If left empty (default value), the name "contour-envoy-healthcheck" will be used. +- `intervalSeconds`: The interval (seconds) between health checks. Defaults to 5 seconds if not set. +- `timeoutSeconds`: The time to wait (seconds) for a health check response. If the timeout is reached the health check attempt will be considered a failure. Defaults to 2 seconds if not set. +- `unhealthyThresholdCount`: The number of unhealthy health checks required before a host is marked unhealthy. Note that for http health checking if a host responds with 503 this threshold is ignored and the host is considered unhealthy immediately. Defaults to 3 if not defined. +- `healthyThresholdCount`: The number of healthy health checks required before a host is marked healthy. Note that during startup, only a single successful health check is required to mark a host healthy. + +## TCP Proxy Health Checking + +Contour also supports TCP health checking and can be configured with various settings to tune the behavior. + +During TCP health checking Envoy will send a connect-only health check to the upstream Endpoints. +It is important to note that these are health checks which Envoy implements and are separate from any +other system such as those that exist in Kubernetes. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: tcp-health-check + namespace: default +spec: + virtualhost: + fqdn: health.bar.com + tcpproxy: + healthCheckPolicy: + intervalSeconds: 5 + timeoutSeconds: 2 + unhealthyThresholdCount: 3 + healthyThresholdCount: 5 + services: + - name: s1-health + port: 80 + - name: s2-health + port: 80 +``` + +TCP Health check policy configuration parameters: + +- `intervalSeconds`: The interval (seconds) between health checks. Defaults to 5 seconds if not set. +- `timeoutSeconds`: The time to wait (seconds) for a health check response. If the timeout is reached the health check attempt will be considered a failure. Defaults to 2 seconds if not set. +- `unhealthyThresholdCount`: The number of unhealthy health checks required before a host is marked unhealthy. Note that for http health checking if a host responds with 503 this threshold is ignored and the host is considered unhealthy immediately. Defaults to 3 if not defined. +- `healthyThresholdCount`: The number of healthy health checks required before a host is marked healthy. Note that during startup, only a single successful health check is required to mark a host healthy. diff --git a/site/content/docs/v1.20.0/config/inclusion-delegation.md b/site/content/docs/v1.20.0/config/inclusion-delegation.md new file mode 100644 index 00000000000..2c5ee7344d0 --- /dev/null +++ b/site/content/docs/v1.20.0/config/inclusion-delegation.md @@ -0,0 +1,135 @@ +# HTTPProxy Inclusion + +HTTPProxy permits the splitting of a system's configuration into separate HTTPProxy instances using **inclusion**. + +Inclusion, as the name implies, allows for one HTTPProxy object to be included in another, optionally with some conditions inherited from the parent. +Contour reads the inclusion tree and merges the included routes into one big object internally before rendering Envoy config. +Importantly, the included HTTPProxy objects do not have to be in the same namespace. + +Each tree of HTTPProxy starts with a root, the top level object of the configuration for a particular virtual host. +Each root HTTPProxy defines a `virtualhost` key, which describes properties such as the fully qualified name of the virtual host, TLS configuration, etc. + +HTTPProxies included from the root must not contain a virtualhost key. +Root objects cannot include other roots either transitively or directly. +This permits the owner of an HTTPProxy root to allow the inclusion of a portion of the route space inside a virtual host, and to allow that route space to be further subdivided with inclusions. +Because the path is not necessarily used as the only key, the route space can be multi-dimensional. + +## Conditions and Inclusion + +Like Routes, Inclusion may specify a set of [conditions][1]. +These conditions are added to any conditions on the routes included. +This process is recursive. + +Conditions are sets of individual condition statements, for example `prefix: /blog` is the condition that the matching request's path must start with `/blog`. +When conditions are combined through inclusion Contour merges the conditions inherited via inclusion with any conditions specified on the route. +This may result in duplicates, for example two `prefix:` conditions, or two header match conditions with the same name and value. +To resolve this Contour applies the following logic. + +- `prefix:` conditions are concatenated together in the order they were applied from the root object. For example the conditions, `prefix: /api`, `prefix: /v1` becomes a single `prefix: /api/v1` conditions. Note: Multiple prefixes cannot be supplied on a single set of Route conditions. +- Proxies with repeated identical `header:` conditions of type "exact match" (the same header keys exactly) are marked as "Invalid" since they create an un-routable configuration. + +## Configuring Inclusion + +Inclusion is a top-level field in the HTTPProxy [spec][2] element. +It requires one field, `name`, and has two optional fields: + +- `namespace`. This will assume the included HTTPProxy is in the same namespace if it's not specified. +- a `conditions` block. + +## Inclusion Within the Same Namespace + +HTTPProxies can include other HTTPProxy objects in the namespace by specifying the name of the object and its namespace in the top-level `includes` block. +Note that `includes` is a list, and so it must use the YAML list construct. + +In this example, the HTTPProxy `include-root` has included the configuration for paths matching `/service2` from the HTTPProxy named `service2` in the same namespace as `include-root` (the `default` namespace). +It's important to note that `service2` HTTPProxy has not defined a `virtualhost` property as it is NOT a root HTTPProxy. + +```yaml +# httpproxy-inclusion-samenamespace.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: include-root + namespace: default +spec: + virtualhost: + fqdn: root.bar.com + includes: + # Includes the /service2 path from service2 in the same namespace + - name: service2 + namespace: default + conditions: + - prefix: /service2 + routes: + - conditions: + - prefix: / + services: + - name: s1 + port: 80 +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: service2 + namespace: default +spec: + routes: + - services: # matches /service2 + - name: s2 + port: 80 + - conditions: + - prefix: /blog # matches /service2/blog + services: + - name: blog + port: 80 +``` + +## Inclusion Across Namespaces + +Inclusion can also happen across Namespaces by specifying a `namespace` in the `inclusion`. +This is a particularly powerful paradigm for enabling multi-team Ingress management. + +In this example, the root HTTPProxy has included configuration for paths matching `/blog` to the `blog` HTTPProxy object in the `marketing` namespace. + +```yaml +# httpproxy-inclusion-across-namespaces.yaml +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: namespace-include-root + namespace: default +spec: + virtualhost: + fqdn: ns-root.bar.com + includes: + # delegate the subpath, `/blog` to the HTTPProxy object in the marketing namespace with the name `blog` + - name: blog + namespace: marketing + conditions: + - prefix: /blog + routes: + - services: + - name: s1 + port: 80 + +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: blog + namespace: marketing +spec: + routes: + - services: + - name: s2 + port: 80 +``` + +## Orphaned HTTPProxy children + +It is possible for HTTPProxy objects to exist that have not been delegated to by another HTTPProxy. +These objects are considered "orphaned" and will be ignored by Contour in determining ingress configuration. + +[1]: request-routing#conditions +[2]: api/#projectcontour.io/v1.HTTPProxySpec diff --git a/site/content/docs/v1.20.0/config/ingress.md b/site/content/docs/v1.20.0/config/ingress.md new file mode 100644 index 00000000000..c8360fc7d7f --- /dev/null +++ b/site/content/docs/v1.20.0/config/ingress.md @@ -0,0 +1,92 @@ +# k8s Ingress Resource Support in Contour + + + + + +This document describes Contour's implementation of specific Ingress resource fields and features. +As the Ingress specification has evolved between v1beta1 and v1, any differences between versions are highlighted to ensure clarity for Contour users. + +**Note: As of Contour version 1.16.0, Contour is not compatible with Kubernetes versions that predate Ingress v1. This means Contour 1.16.0 and above require Kubernetes 1.19 and above. The Ingress v1beta1 resource is still available in Kubernetes 1.19 (but will be removed in 1.22) and the API server will convert such resources to Ingress v1 for Contour to subscribe to.** + +## Kubernetes Versions + +Contour is [validated against Kubernetes release versions N through N-2][1] (with N being the latest release). +For Kubernetes version 1.19+, the API server translates any Ingress v1beta1 resources to Ingress v1 and Contour watches Ingress v1 resources. + +## IngressClass and IngressClass Name + +In order to support differentiating between Ingress controllers or multiple instances of a single Ingress controller, users can create an [IngressClass resource][2] and specify an IngressClass name on a Ingress to reference it. +The IngressClass resource can be used to provide configuration to an Ingress controller watching resources it governs. +Contour supports watching an IngressClass resource specified with the `--ingress-class-name` flag to the `contour serve` command. +Contour does not require an IngressClass resource with the name passed in the aforementioned flag to exist, the name can just be used as an identifier for filtering which Ingress resources Contour reconciles into actual route configuration. + +Ingresses may specify an IngressClass name via the original annotation method or via the `ingressClassName` spec field. +As the `ingressClassName` field has been introduced on Ingress v1beta1, there should be no differences in IngressClass name filtering between the two available versions of the resource. +Contour uses its configured IngressClass name to filter Ingresses. +If the `--ingress-class-name` flag is provided, Contour will only accept Ingress resources that exactly match the specified IngressClass name via annotation or spec field, with the value in the annotation taking precedence. +If the flag is not passed to `contour serve` Contour will accept any Ingress resource that specifies the IngressClass name `contour` in annotation or spec fields or does not specify one at all. + +## Default Backend + +Contour supports the `defaultBackend` Ingress v1 spec field and equivalent `backend` v1beta1 version of the field. +See upstream [documentation][3] on this field. +Any requests that do not match an Ingress rule will be forwarded to this backend. +As TLS secrets on Ingresses are scoped to specific hosts, this default backend cannot serve TLS as it could match an unbounded set of hosts and configuring a matching set of TLS secrets would not be possible. +As is the case on Ingress rules, Contour only supports configuring a Service as a backend and does not support any other Kubernetes resource. + +## Ingress Rules + +See upstream [documentation][4] on Ingress rules. + +As with default backends, Contour only supports configuring a Service as a backend and does not support any other Kubernetes resource. + +Contour supports [wildcard hostnames][5] as documented by the upstream API as well as precise hostnames. +Wildcard hostnames are limited to the whole first DNS label of the hostname, e.g. `*.foo.com` is valid but `*foo.com`, `foo*.com`, `foo.*.com` are not. +`*` is also not a valid hostname. +The Ingress admission controller validation ensures valid hostnames are present when creating an Ingress resource. + +Contour supports all of the various [path matching][6] types described by the Ingress spec. +Prior to Contour 1.14.0, path match types were ignored and path matching was performed with a Contour specific implementation. +Paths specified with any regex meta-characters (any of `^+*[]%`) were implemented as regex matches. +Any other paths were programmed in Envoy as "string prefix" matches. +This behavior is preserved in the `ImplementationSpecific` match type in Contour 1.14.0+ to ensure backwards compatibility. +`Exact` path matches will now result in matching requests to the given path exactly. +The `Prefix` patch match type will now result in matching requests with a "segment prefix" rather than a "string prefix" according to the spec (e.g. the prefix `/foo/bar` will match requests with paths `/foo/bar`, `/foo/bar/`, and `/foo/bar/baz`, but not `/foo/barbaz`). + +## TLS + +See upstream [documentation][7] on TLS configuration. + +A secret specified in an Ingress TLS element will only be applied to Ingress rules with `Host` configuration that exactly matches an element of the TLS `Hosts` field. +Any secrets that do not match an Ingress rule `Host` will be ignored. + +In Ingress v1beta1, the `secretName` field could contain a string with a full `namespace/name` identifier. +When used with Contour's [TLS certificate delegation][8], this allowed Ingresses to use a TLS certificate from a different namespace. +However, Ingress v1 does not allow the `secretName` field to contain a string with a full `namespace/name` identifier, because the field validation disallows the `/` character. +Instead, Ingress v1 resources can now use the `projectcontour.io/tls-cert-namespace` annotation, to define the namespace that contains the TLS certificate (if different than the Ingress's namespace). +This enables the TLS certificate delegation functionality to continue working for Ingress v1. +For more information and an example, see the [TLS certificate delegation documentation][8]. + +## Status + +In order to inform users of the address the Services their Ingress resources can be accessed at, Contour sets status on Ingress resources. +If `contour serve` is run with the `--ingress-status-address` flag, Contour will use the provided value to set the Ingress status address accordingly. +If not provided, Contour will use the address of the Envoy service using the passed in `--envoy-service-name` and `--envoy-service-namespace` flags. + +## Header Manipulation + +The Ingress resource does not allow adding or removing HTTP headers on requests or responses. +However, Contour does allow users to set a global HTTP header [policy configuration][9] which can be optionally applied to configuration generated from Ingress resources. +Contour enables this behavior with the `applyToIngress` boolean field (set to `true` to enable). + +[0]: https://github.com/kubernetes-sigs/ingress-controller-conformance +[1]: /resources/compatibility-matrix/ +[2]: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class +[3]: https://kubernetes.io/docs/concepts/services-networking/ingress/#default-backend +[4]: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules +[5]: https://kubernetes.io/docs/concepts/services-networking/ingress/#hostname-wildcards +[6]: https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types +[7]: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls +[8]: /docs/{{< param version >}}/config/tls-delegation/ +[9]: /docs/{{< param version >}}/configuration/#policy-configuration diff --git a/site/content/docs/v1.20.0/config/rate-limiting.md b/site/content/docs/v1.20.0/config/rate-limiting.md new file mode 100644 index 00000000000..3a607bb312f --- /dev/null +++ b/site/content/docs/v1.20.0/config/rate-limiting.md @@ -0,0 +1,366 @@ +# Rate Limiting + +- [Overview](#overview) +- [Local Rate Limiting](#local-rate-limiting) +- [Global Rate Limiting](#global-rate-limiting) + +## Overview + +Rate limiting is a means of protecting backend services against unwanted traffic. +This can be useful for a variety of different scenarios: + +- Protecting against denial-of-service (DoS) attacks by malicious actors +- Protecting against DoS incidents due to bugs in client applications/services +- Enforcing usage quotas for different classes of clients, e.g. free vs. paid tiers +- Controlling resource consumption/cost + +Envoy supports two forms of HTTP rate limiting: **local** and **global**. + +In local rate limiting, rate limits are enforced by each Envoy instance, without any communication with other Envoys or any external service. + +In global rate limiting, an external rate limit service (RLS) is queried by each Envoy via gRPC for rate limit decisions. + +Contour supports both forms of Envoy's rate limiting. + +## Local Rate Limiting + +The `HTTPProxy` API supports defining local rate limit policies that can be applied to either individual routes or entire virtual hosts. +Local rate limit policies define a maximum number of requests per unit of time that an Envoy should proxy to the upstream service. +Requests beyond the defined limit will receive a `429 (Too Many Requests)` response by default. +Local rate limit policies program Envoy's [HTTP local rate limit filter][1]. + +It's important to note that local rate limit policies apply *per Envoy pod*. +For example, a local rate limit policy of 100 requests per second for a given route will result in *each Envoy pod* allowing up to 100 requests per second for that route. + +### Defining a local rate limit + +Local rate limit policies can be defined for either routes or virtual hosts. A local rate limit policy requires a `requests` and a `units` field, defining the *number of requests per unit of time* that are allowed. `Requests` must be a positive integer, and `units` can be `second`, `minute`, or `hour`. Optionally, a `burst` parameter can also be provided, defining the number of requests above the baseline rate that are allowed in a short period of time. This would allow occasional larger bursts of traffic not to be rate limited. + +Local rate limiting for the virtual host: +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: default + name: ratelimited-vhost +spec: + virtualhost: + fqdn: local.projectcontour.io + rateLimitPolicy: + local: + requests: 100 + unit: hour + burst: 20 + routes: + - conditions: + - prefix: /s1 + services: + - name: s1 + port: 80 + - conditions: + - prefix: /s2 + services: + - name: s2 + port: 80 +``` + +Local rate limiting for the route: +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: default + name: ratelimited-route +spec: + virtualhost: + fqdn: local.projectcontour.io + routes: + - conditions: + - prefix: /s1 + services: + - name: s1 + port: 80 + rateLimitPolicy: + local: + requests: 20 + unit: minute + - conditions: + - prefix: /s2 + services: + - name: s2 + port: 80 +``` + +### Customizing the response + +#### Response code + +By default, Envoy returns a `429 (Too Many Requests)` when a request is rate limited. +A non-default response code can optionally be configured as part of the local rate limit policy, in the `responseStatusCode` field. +The value must be in the 400-599 range. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: default + name: custom-ratelimit-response +spec: + virtualhost: + fqdn: local.projectcontour.io + routes: + - conditions: + - prefix: /s1 + services: + - name: s1 + port: 80 + rateLimitPolicy: + local: + requests: 20 + unit: minute + responseStatusCode: 503 # Service Unavailable +``` + +#### Headers + +Headers can optionally be added to rate limited responses, by configuring the `responseHeadersToAdd` field. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: default + name: custom-ratelimit-response +spec: + virtualhost: + fqdn: local.projectcontour.io + routes: + - conditions: + - prefix: /s1 + services: + - name: s1 + port: 80 + rateLimitPolicy: + local: + requests: 20 + unit: minute + responseHeadersToAdd: + - name: x-contour-ratelimited + value: "true" +``` + +## Global Rate Limiting + +The `HTTPProxy` API also supports defining global rate limit policies on routes and virtual hosts. + +In order to use global rate limiting, you must first select and deploy an external rate limit service (RLS). +There is an [Envoy rate limit service implementation][2], but any service that implements the [RateLimitService gRPC interface][3] is supported. + +### Configuring an exernal RLS with Contour + +Once you have deployed your RLS, you must configure it with Contour. + +Define an extension service for it (substituting values as appropriate): +```yaml +apiVersion: projectcontour.io/v1alpha1 +kind: ExtensionService +metadata: + namespace: projectcontour + name: ratelimit +spec: + protocol: h2 + services: + - name: ratelimit + port: 8081 +``` + +Now add a reference to it in the Contour config file: +```yaml +rateLimitService: + # The namespace/name of the extension service. + extensionService: projectcontour/ratelimit + # The domain value to pass to the RLS for all rate limit + # requests. Acts as a container for a set of rate limit + # definitions within the RLS. + domain: contour + # Whether to allow requests to proceed when the rate limit + # service fails to respond with a valid rate limit decision + # within the timeout defined on the extension service. + failOpen: true +``` + +### Defining a global rate limit policy + +Global rate limit policies can be defined for either routes or virtual hosts. Unlike local rate limit policies, global rate limit policies do not directly define a rate limit. Instead, they define a set of request descriptors that will be generated and sent to the external RLS for each request. The external RLS then makes the rate limit decision based on the descriptors and returns a response to Envoy. + +A global rate limit policy for the virtual host: +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: default + name: ratelimited-vhost +spec: + virtualhost: + fqdn: local.projectcontour.io + rateLimitPolicy: + global: + descriptors: + # the first descriptor has a single key-value pair: + # [ remote_address= ]. + - entries: + - remoteAddress: {} + # the second descriptor has two key-value pairs: + # [ remote_address=, vhost=local.projectcontour.io ]. + - entries: + - remoteAddress: {} + - genericKey: + key: vhost + value: local.projectcontour.io + routes: + - conditions: + - prefix: /s1 + services: + - name: s1 + port: 80 + - conditions: + - prefix: /s2 + services: + - name: s2 + port: 80 +``` + +A global rate limit policy for the route: +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + namespace: default + name: ratelimited-route +spec: + virtualhost: + fqdn: local.projectcontour.io + routes: + - conditions: + - prefix: /s1 + services: + - name: s1 + port: 80 + rateLimitPolicy: + global: + descriptors: + # the first descriptor has a single key-value pair: + # [ remote_address= ]. + - entries: + - remoteAddress: {} + # the second descriptor has two key-value pairs: + # [ remote_address=, prefix=/s1 ]. + - entries: + - remoteAddress: {} + - genericKey: + key: prefix + value: /s1 + - conditions: + - prefix: /s2 + services: + - name: s2 + port: 80 +``` + +#### Descriptors & descriptor entries + +A descriptor is a list of key-value pairs, i.e. entries, that are generated for a request. The entries can be generated based on different criteria. If any entry in a descriptor cannot generate a key-value pair for a given request, then the entire descriptor is not generated (see the [Envoy documentation][8] for more information). When a global rate limit policy defines multiple descriptors, then *all* descriptors that can be generated will be generated and sent to the rate limit service for consideration. + +Below are the supported types of descriptor entries. + +##### GenericKey + +A `GenericKey` descriptor entry defines a static key-value pair. For example: + +```yaml +rateLimitPolicy: + global: + descriptors: + - entries: + - genericKey: + key: virtual-host-name + value: foo.bar.com +``` + +Produces a descriptor entry of `virtual-host-name=foo.bar.com`. + +The `key` field is optional and defaults to a value of `generic_key` if not specified. + +See the [Envoy documentation][4] for more information and examples. + +##### RemoteAddress + +A `RemoteAddress` descriptor entry has a key of `remote_address` and a value of the client IP address (using the trusted address from `x-forwarded-for`). For example: + +```yaml +rateLimitPolicy: + global: + descriptors: + - entries: + - remoteAddress: {} +``` + +Produces a descriptor entry of `remote_address=`. + +See the [Envoy documentation][5] for more information and examples. + +##### RequestHeader + +A `RequestHeader` descriptor entry has a static key and a value equal to the value of a specified header on the client request. If the header is not present, the descriptor entry is not generated. For example: + +```yaml +rateLimitPolicy: + global: + descriptors: + - entries: + - requestHeader: + headerName: My-Header + descriptorKey: my-header-value +``` + +Produces a descriptor entry of `my-header-value=`, for a client request that has the `My-Header` header. + +See the [Envoy documentation][6] for more information and examples. + +##### RequestHeaderValueMatch + +A `RequestHeaderValueMatch` descriptor entry has a key of `header_match` and a static value. The entry is only generated if the client request's headers match a specified set of criteria. For example: + +```yaml +rateLimitPolicy: + global: + descriptors: + - entries: + - requestHeaderValueMatch: + headers: + - name: My-Header + notpresent: true + - name: My-Other-Header + contains: contour + expectMatch: true + value: foo +``` + +Produces a descriptor entry of `header_match=foo`, for a client request that does not have the `My-Header` header, and does have the `My-Other-Header` header, with a value containing the substring "contour". + +Contour supports `present`, `notpresent`, `contains`, `notcontains`, `exact`, and `notexact` header match operators. + +The `expectMatch` field defaults to true if not specified. If true, the client request's headers must positively match the specified criteria in order for the descriptor entry to be generated. If false, the client request's header must *not* match the specified criteria in order for the descriptor entry to be generated. + +See the [Envoy documentation][7] for more information and examples. + + + +[1]: https://www.envoyproxy.io/docs/envoy/v1.17.0/configuration/http/http_filters/local_rate_limit_filter#config-http-filters-local-rate-limit +[2]: https://github.com/envoyproxy/ratelimit +[3]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ratelimit/v3/rls.proto +[4]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-ratelimit-action-generickey +[5]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-ratelimit-action-remoteaddress +[6]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-ratelimit-action-requestheaders +[7]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-ratelimit-action-headervaluematch +[8]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/rate_limit_filter#composing-actions diff --git a/site/content/docs/v1.20.0/config/request-rewriting.md b/site/content/docs/v1.20.0/config/request-rewriting.md new file mode 100644 index 00000000000..a01dd78bcae --- /dev/null +++ b/site/content/docs/v1.20.0/config/request-rewriting.md @@ -0,0 +1,259 @@ +# Request Rewriting + +## Path Rewriting + +HTTPProxy supports rewriting the HTTP request URL path prior to delivering the request to the backend service. +Rewriting is performed after a routing decision has been made, and never changes the request destination. + +The `pathRewritePolicy` field specifies how the path prefix should be rewritten. +The `replacePrefix` rewrite policy specifies a replacement string for a HTTP request path prefix match. +When this field is present, the path prefix that the request matched is replaced by the text specified in the `replacement` field. +If the HTTP request path is longer than the matched prefix, the remainder of the path is unchanged. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: rewrite-example + namespace: default +spec: + virtualhost: + fqdn: rewrite.bar.com + routes: + - services: + - name: s1 + port: 80 + pathRewritePolicy: + replacePrefix: + - replacement: /new/prefix +``` + +The `replacePrefix` field accepts an array of possible replacements. +When more than one `replacePrefix` array element is present, the `prefix` field can be used to disambiguate which replacement to apply. + +If no `prefix` field is present, the replacement is applied to all prefix matches made against the route. +If a `prefix` field is present, the replacement is applied only to routes that have an exactly matching prefix condition. +Specifying more than one `replacePrefix` entry is mainly useful when a HTTPProxy document is included into multiple parent documents. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: rewrite-example + namespace: default +spec: + virtualhost: + fqdn: rewrite.bar.com + routes: + - services: + - name: s1 + port: 80 + conditions: + - prefix: /v1/api + pathRewritePolicy: + replacePrefix: + - prefix: /v1/api + replacement: /app/api/v1 + - prefix: / + replacement: /app +``` + +## Header Rewriting + +HTTPProxy supports rewriting HTTP request and response headers. +The `Set` operation sets a HTTP header value, creating it if it doesn't already exist or overwriting it if it does. +The `Remove` operation removes a HTTP header. +The `requestHeadersPolicy` field is used to rewrite headers on a HTTP request, and the `responseHeadersPolicy` is used to rewrite headers on a HTTP response. +These fields can be specified on a route or on a specific service, depending on the rewrite granularity you need. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: header-rewrite-example +spec: + virtualhost: + fqdn: header.bar.com + routes: + - services: + - name: s1 + port: 80 + requestHeadersPolicy: + set: + - name: Host + value: external.dev + remove: + - Some-Header + - Some-Other-Header +``` + +Manipulating headers is also supported per-Service or per-Route. Headers can be set or +removed from the request or response as follows: + +per-Service: + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: header-manipulation + namespace: default +spec: + virtualhost: + fqdn: headers.bar.com + routes: + - services: + - name: s1 + port: 80 + requestHeadersPolicy: + set: + - name: X-Foo + value: bar + remove: + - X-Baz + responseHeadersPolicy: + set: + - name: X-Service-Name + value: s1 + remove: + - X-Internal-Secret +``` + +per-Route: + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: header-manipulation + namespace: default +spec: + virtualhost: + fqdn: headers.bar.com + routes: + - services: + - name: s1 + port: 80 + requestHeadersPolicy: + set: + - name: X-Foo + value: bar + remove: + - X-Baz + responseHeadersPolicy: + set: + - name: X-Service-Name + value: s1 + remove: + - X-Internal-Secret +``` + +In these examples we are setting the header `X-Foo` with value `baz` on requests +and stripping `X-Baz`. We are then setting `X-Service-Name` on the response with +value `s1`, and removing `X-Internal-Secret`. + +### Dynamic Header Values + +It is sometimes useful to set a header value using a dynamic value such as the +hostname where the Envoy Pod is running (`%HOSTNAME%`) or the subject of the +TLS client certificate (`%DOWNSTREAM_PEER_SUBJECT%`) or based on another header +(`%REQ(header)%`). + +Examples: +``` + requestHeadersPolicy: + set: + - name: X-Envoy-Hostname + value: "%HOSTNAME%" + - name: X-Host-Protocol + value: "%REQ(Host)% - %PROTOCOL%" + responseHeadersPolicy: + set: + - name: X-Envoy-Response-Flags + value: "%RESPONSE_FLAGS%" +``` + +Contour supports most of the custom request/response header variables offered +by Envoy - see the Envoy +documentation for details of what each of these resolve to: + +* `%DOWNSTREAM_REMOTE_ADDRESS%` +* `%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%` +* `%DOWNSTREAM_LOCAL_ADDRESS%` +* `%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%` +* `%DOWNSTREAM_LOCAL_PORT%` +* `%DOWNSTREAM_LOCAL_URI_SAN%` +* `%DOWNSTREAM_PEER_URI_SAN%` +* `%DOWNSTREAM_LOCAL_SUBJECT%` +* `%DOWNSTREAM_PEER_SUBJECT%` +* `%DOWNSTREAM_PEER_ISSUER%` +* `%DOWNSTREAM_TLS_SESSION_ID%` +* `%DOWNSTREAM_TLS_CIPHER%` +* `%DOWNSTREAM_TLS_VERSION%` +* `%DOWNSTREAM_PEER_FINGERPRINT_256%` +* `%DOWNSTREAM_PEER_FINGERPRINT_1%` +* `%DOWNSTREAM_PEER_SERIAL%` +* `%DOWNSTREAM_PEER_CERT%` +* `%DOWNSTREAM_PEER_CERT_V_START%` +* `%DOWNSTREAM_PEER_CERT_V_END%` +* `%HOSTNAME%` +* `%REQ(header-name)%` +* `%PROTOCOL%` +* `%RESPONSE_FLAGS%` +* `%RESPONSE_CODE_DETAILS%` +* `%UPSTREAM_REMOTE_ADDRESS%` + +Note that Envoy passes variables that can't be expanded through unchanged or +skips them entirely - for example: +* `%UPSTREAM_REMOTE_ADDRESS%` as a request header remains as + `%UPSTREAM_REMOTE_ADDRESS%` because as noted in the Envoy docs: "The upstream + remote address cannot be added to request headers as the upstream host has not + been selected when custom request headers are generated." +* `%DOWNSTREAM_TLS_VERSION%` is skipped if TLS is not in use +* Envoy ignores REQ headers that refer to an non-existent header - for example + `%REQ(Host)%` works as expected but `%REQ(Missing-Header)%` is skipped + +Contour already sets the `X-Request-Start` request header to +`t=%START_TIME(%s.%3f)%` which is the Unix epoch time when the request +started. + +To enable setting header values based on the destination service Contour also supports: + +* `%CONTOUR_NAMESPACE%` +* `%CONTOUR_SERVICE_NAME%` +* `%CONTOUR_SERVICE_PORT%` + +For example, with the following HTTPProxy object that has a per-Service requestHeadersPolicy using these variables: +``` +# httpproxy.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: basic + namespace: myns +spec: + virtualhost: + fqdn: foo-basic.bar.com + routes: + - conditions: + - prefix: / + services: + - name: s1 + port: 80 + requestHeadersPolicy: + set: + - name: l5d-dst-override + value: "%CONTOUR_SERVICE_NAME%.%CONTOUR_NAMESPACE%.svc.cluster.local:%CONTOUR_SERVICE_PORT%" +``` +the values would be: +* `CONTOUR_NAMESPACE: "myns"` +* `CONTOUR_SERVICE_NAME: "s1"` +* `CONTOUR_SERVICE_PORT: "80"` + +and the `l5-dst-override` header would be set to `s1.myns.svc.cluster.local:80`. + +For per-Route requestHeadersPolicy only `%CONTOUR_NAMESPACE%` is set and using +`%CONTOUR_SERVICE_NAME%` and `%CONTOUR_SERVICE_PORT%` will end up as the +literal values `%%CONTOUR_SERVICE_NAME%%` and `%%CONTOUR_SERVICE_PORT%%`, +respectively. diff --git a/site/content/docs/v1.20.0/config/request-routing.md b/site/content/docs/v1.20.0/config/request-routing.md new file mode 100644 index 00000000000..16cd9541d43 --- /dev/null +++ b/site/content/docs/v1.20.0/config/request-routing.md @@ -0,0 +1,322 @@ +# Request Routing + +A HTTPProxy object must have at least one route or include defined. +In this example, any requests to `multi-path.bar.com/blog` or `multi-path.bar.com/blog/*` will be routed to the Service `s2`. +All other requests to the host `multi-path.bar.com` will be routed to the Service `s1`. + +```yaml +# httpproxy-multiple-paths.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: multiple-paths + namespace: default +spec: + virtualhost: + fqdn: multi-path.bar.com + routes: + - conditions: + - prefix: / # matches everything else + services: + - name: s1 + port: 80 + - conditions: + - prefix: /blog # matches `multi-path.bar.com/blog` or `multi-path.bar.com/blog/*` + services: + - name: s2 + port: 80 +``` + +In the following example, we match on headers and send to different services, with a default route if those do not match. + +```yaml +# httpproxy-multiple-headers.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: multiple-paths + namespace: default +spec: + virtualhost: + fqdn: multi-path.bar.com + routes: + - conditions: + - header: + name: x-os + contains: ios + services: + - name: s1 + port: 80 + - conditions: + - header: + name: x-os + contains: android + services: + - name: s2 + port: 80 + - services: + - name: s3 + port: 80 +``` + +## Conditions + +Each Route entry in a HTTPProxy **may** contain one or more conditions. +These conditions are combined with an AND operator on the route passed to Envoy. +Conditions can be either a `prefix` or a `header` condition. + +#### Prefix conditions + +Paths defined are matched using prefix conditions. +Up to one prefix condition may be present in any condition block. + +Prefix conditions **must** start with a `/` if they are present. + +#### Header conditions + +For `header` conditions there is one required field, `name`, and six operator fields: `present`, `notpresent`, `contains`, `notcontains`, `exact`, and `notexact`. + +- `present` is a boolean and checks that the header is present. The value will not be checked. + +- `notpresent` similarly checks that the header is *not* present. + +- `contains` is a string, and checks that the header contains the string. `notcontains` similarly checks that the header does *not* contain the string. + +- `exact` is a string, and checks that the header exactly matches the whole string. `notexact` checks that the header does *not* exactly match the whole string. + +## Multiple Upstreams + +One of the key HTTPProxy features is the ability to support multiple services for a given path: + +```yaml +# httpproxy-multiple-upstreams.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: multiple-upstreams + namespace: default +spec: + virtualhost: + fqdn: multi.bar.com + routes: + - services: + - name: s1 + port: 80 + - name: s2 + port: 80 +``` + +In this example, requests for `multi.bar.com/` will be load balanced across two Kubernetes Services, `s1`, and `s2`. +This is helpful when you need to split traffic for a given URL across two different versions of an application. + +### Upstream Weighting + +Building on multiple upstreams is the ability to define relative weights for upstream Services. +This is commonly used for canary testing of new versions of an application when you want to send a small fraction of traffic to a specific Service. + +```yaml +# httpproxy-weight-shfiting.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: weight-shifting + namespace: default +spec: + virtualhost: + fqdn: weights.bar.com + routes: + - services: + - name: s1 + port: 80 + weight: 10 + - name: s2 + port: 80 + weight: 90 +``` + +In this example, we are sending 10% of the traffic to Service `s1`, while Service `s2` receives the remaining 90% of traffic. + +HTTPProxy weighting follows some specific rules: + +- If no weights are specified for a given route, it's assumed even distribution across the Services. +- Weights are relative and do not need to add up to 100. If all weights for a route are specified, then the "total" weight is the sum of those specified. As an example, if weights are 20, 30, 20 for three upstreams, the total weight would be 70. In this example, a weight of 30 would receive approximately 42.9% of traffic (30/70 = .4285). +- If some weights are specified but others are not, then it's assumed that upstreams without weights have an implicit weight of zero, and thus will not receive traffic. + +### Traffic mirroring + +Per route, a service can be nominated as a mirror. +The mirror service will receive a copy of the read traffic sent to any non mirror service. +The mirror traffic is considered _read only_, any response by the mirror will be discarded. + +This service can be useful for recording traffic for later replay or for smoke testing new deployments. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: traffic-mirror + namespace: default +spec: + virtualhost: + fqdn: www.example.com + routes: + - conditions: + - prefix: / + services: + - name: www + port: 80 + - name: www-mirror + port: 80 + mirror: true +``` + +## Response Timeouts + +Each Route can be configured to have a timeout policy and a retry policy as shown: + +```yaml +# httpproxy-response-timeout.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: response-timeout + namespace: default +spec: + virtualhost: + fqdn: timeout.bar.com + routes: + - timeoutPolicy: + response: 1s + idle: 10s + retryPolicy: + count: 3 + perTryTimeout: 150ms + services: + - name: s1 + port: 80 +``` + +In this example, requests to `timeout.bar.com/` will have a response timeout policy of 1s. +This refers to the time that spans between the point at which complete client request has been processed by the proxy, and when the response from the server has been completely processed. + +- `timeoutPolicy.response` Timeout for receiving a response from the server after processing a request from client. +If not supplied, Envoy's default value of 15s applies. +More information can be found in [Envoy's documentation][4]. +- `timeoutPolicy.idle` Timeout for how long the proxy should wait while there is no activity during single request/response (for HTTP/1.1) or stream (for HTTP/2). +Timeout will not trigger while HTTP/1.1 connection is idle between two consecutive requests. +If not specified, there is no per-route idle timeout, though a connection manager-wide stream idle timeout default of 5m still applies. +More information can be found in [Envoy's documentation][6]. + +TimeoutPolicy durations are expressed in the Go [Duration format][5]. +Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +The string "infinity" is also a valid input and specifies no timeout. +A value of "0s" will be treated as if the field were not set, i.e. by using Envoy's default behavior. +Example input values: "300ms", "5s", "1m". + +- `retryPolicy`: A retry will be attempted if the server returns an error code in the 5xx range, or if the server takes more than `retryPolicy.perTryTimeout` to process a request. + +- `retryPolicy.count` specifies the maximum number of retries allowed. This parameter is optional and defaults to 1. Set to -1 to disable. If set to 0, the Envoy default of 1 is used. + +- `retryPolicy.perTryTimeout` specifies the timeout per retry. If this field is greater than the request timeout, it is ignored. This parameter is optional. + If left unspecified, `timeoutPolicy.request` will be used. + +## Load Balancing Strategy + +Each route can have a load balancing strategy applied to determine which of its Endpoints is selected for the request. +The following list are the options available to choose from: + +- `RoundRobin`: Each healthy upstream Endpoint is selected in round robin order (Default strategy if none selected). +- `WeightedLeastRequest`: The least request load balancer uses different algorithms depending on whether hosts have the same or different weights in an attempt to route traffic based upon the number of active requests or the load at the time of selection. +- `Random`: The random strategy selects a random healthy Endpoints. +- `RequestHash`: The request hashing strategy allows for load balancing based on request attributes. An upstream Endpoint is selected based on the hash of an element of a request. For example, requests that contain a consistent value in a HTTP request header will be routed to the same upstream Endpoint. Currently only hashing of HTTP request headers and the source IP of a request is supported. +- `Cookie`: The cookie load balancing strategy is similar to the request hash strategy and is a convenience feature to implement session affinity, as described below. + +More information on the load balancing strategy can be found in [Envoy's documentation][7]. + +The following example defines the strategy for the route `/` as `WeightedLeastRequest`. + +```yaml +# httpproxy-lb-strategy.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: lb-strategy + namespace: default +spec: + virtualhost: + fqdn: strategy.bar.com + routes: + - conditions: + - prefix: / + services: + - name: s1-strategy + port: 80 + - name: s2-strategy + port: 80 + loadBalancerPolicy: + strategy: WeightedLeastRequest +``` + +The below example demonstrates how request hash load balancing policies can be configured: + +```yaml +# httpproxy-lb-request-hash.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: lb-request-hash + namespace: default +spec: + virtualhost: + fqdn: request-hash.bar.com + routes: + - conditions: + - prefix: / + services: + - name: httpbin + port: 8080 + loadBalancerPolicy: + strategy: RequestHash + requestHashPolicies: + - headerHashOptions: + headerName: X-Some-Header + terminal: true + - headerHashOptions: + headerName: User-Agent + - hashSourceIP: true +``` + +In this example, if a client request contains the `X-Some-Header` header, the value of the header will be hashed and used to route to an upstream Endpoint. This could be used to implement a similar workflow to cookie-based session affinity by passing a consistent value for this header. If it is present, because it is set as a `terminal` hash option, Envoy will not continue on to process to `User-Agent` header or source IP to calculate a hash. If `X-Some-Header` is not present, Envoy will use the `User-Agent` header value to make a routing decision along with the source IP of the client making the request. These policies can be used alone or as shown for an advanced routing decision. + +## Session Affinity + +Session affinity, also known as _sticky sessions_, is a load balancing strategy whereby a sequence of requests from a single client are consistently routed to the same application backend. +Contour supports session affinity on a per route basis with `loadBalancerPolicy` `strategy: Cookie`. + +```yaml +# httpproxy-sticky-sessions.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: httpbin + namespace: default +spec: + virtualhost: + fqdn: httpbin.davecheney.com + routes: + - services: + - name: httpbin + port: 8080 + loadBalancerPolicy: + strategy: Cookie +``` + +Session affinity is based on the premise that the backend servers are robust, do not change ordering, or grow and shrink according to load. +None of these properties are guaranteed by a Kubernetes cluster and will be visible to applications that rely heavily on session affinity. + +Any perturbation in the set of pods backing a service risks redistributing backends around the hash ring. + +[4]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-timeout +[5]: https://godoc.org/time#ParseDuration +[6]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-idle-timeout +[7]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/overview diff --git a/site/content/docs/v1.20.0/config/tls-delegation.md b/site/content/docs/v1.20.0/config/tls-delegation.md new file mode 100644 index 00000000000..0c949a212fe --- /dev/null +++ b/site/content/docs/v1.20.0/config/tls-delegation.md @@ -0,0 +1,77 @@ +# TLS Certificate Delegation + +In order to support wildcard certificates, TLS certificates for a `*.somedomain.com`, which are stored in a namespace controlled by the cluster administrator, Contour supports a facility known as TLS Certificate Delegation. +This facility allows the owner of a TLS certificate to delegate, for the purposes of referencing the TLS certificate, permission to Contour to read the Secret object from another namespace. +Delegation works for both HTTPProxy and Ingress resources, however it needs an annotation to work with Ingress v1. + +The [`TLSCertificateDelegation`][1] resource defines a set of `delegations` in the `spec`. +Each delegation references a `secretName` from the namespace where the `TLSCertificateDelegation` is created as well as describing a set of `targetNamespaces` in which the certificate can be referenced. +If all namespaces should be able to reference the secret, then set `"*"` as the value of `targetNamespaces` (see example below). + +```yaml +apiVersion: projectcontour.io/v1 +kind: TLSCertificateDelegation +metadata: + name: example-com-wildcard + namespace: www-admin +spec: + delegations: + - secretName: example-com-wildcard + targetNamespaces: + - example-com + - secretName: another-com-wildcard + targetNamespaces: + - "*" +``` + +In this example, the permission for Contour to reference the Secret `example-com-wildcard` in the `admin` namespace has been delegated to HTTPProxy and Ingress objects in the `example-com` namespace. +Also, the permission for Contour to reference the Secret `another-com-wildcard` from all namespaces has been delegated to all HTTPProxy and Ingress objects in the cluster. + +To reference the secret from an HTTPProxy or Ingress v1beta1 you must use the slash syntax in the `secretName`: +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: www + namespace: example-com +spec: + virtualhost: + fqdn: foo2.bar.com + tls: + secretName: www-admin/example-com-wildcard + routes: + - services: + - name: s1 + port: 80 +``` + +To reference the secret from an Ingress v1 you must use the `projectcontour.io/tls-cert-namespace` annotation: +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + projectcontour.io/tls-cert-namespace: www-admin + name: www + namespace: example-com +spec: + rules: + - host: foo2.bar.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: s1 + port: + number: 80 + tls: + - hosts: + - foo2.bar.com + secretName: example-com-wildcard +``` + + +[0]: https://github.com/projectcontour/contour/issues/3544 +[1]: /docs/{{< param version >}}/config/api/#projectcontour.io/v1.TLSCertificateDelegation diff --git a/site/content/docs/v1.20.0/config/tls-termination.md b/site/content/docs/v1.20.0/config/tls-termination.md new file mode 100644 index 00000000000..6d86c0dbcda --- /dev/null +++ b/site/content/docs/v1.20.0/config/tls-termination.md @@ -0,0 +1,255 @@ +# TLS Termination + +HTTPProxy follows a similar pattern to Ingress for configuring TLS credentials. + +You can secure a HTTPProxy by specifying a Secret that contains TLS private key and certificate information. +If multiple HTTPProxies utilize the same Secret, the certificate must include the necessary Subject Authority Name (SAN) for each fqdn. + +Contour (via Envoy) requires that clients send the Server Name Indication (SNI) TLS extension so that requests can be routed to the correct virtual host. +Virtual hosts are strongly bound to SNI names. +This means that the Host header in HTTP requests must match the SNI name that was sent at the start of the TLS session. + +Contour also follows a "secure first" approach. +When TLS is enabled for a virtual host, any request to the insecure port is redirected to the secure interface with a 301 redirect. +Specific routes can be configured to override this behavior and handle insecure requests by enabling the `spec.routes.permitInsecure` parameter on a Route. + +The TLS secret must: +- be a Secret of type `kubernetes.io/tls`. This means that it must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS, in PEM format. + +The TLS secret may also: +- add any chain CA certificates required for validation into the `tls.crt` PEM bundle. If this is the case, the serving certificate must be the first certificate in the bundle and the intermediate CA certificates must be appended in issuing order. + +```yaml +# ingress-tls.secret.yaml +apiVersion: v1 +data: + tls.crt: base64 encoded cert + tls.key: base64 encoded key +kind: Secret +metadata: + name: testsecret + namespace: default +type: kubernetes.io/tls +``` + +The HTTPProxy can be configured to use this secret using `tls.secretName` property: + +```yaml +# httpproxy-tls.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: tls-example + namespace: default +spec: + virtualhost: + fqdn: foo2.bar.com + tls: + secretName: testsecret + routes: + - services: + - name: s1 + port: 80 +``` + +If the `tls.secretName` property contains a slash, eg. `somenamespace/somesecret` then, subject to TLS Certificate Delegation, the TLS certificate will be read from `somesecret` in `somenamespace`. +See TLS Certificate Delegation below for more information. + +The TLS **Minimum Protocol Version** a virtual host should negotiate can be specified by setting the `spec.virtualhost.tls.minimumProtocolVersion`: + +- 1.3 +- 1.2 (Default) + +## Fallback Certificate + +Contour provides virtual host based routing, so that any TLS request is routed to the appropriate service based on both the server name requested by the TLS client and the HOST header in the HTTP request. + +Since the HOST Header is encrypted during TLS handshake, it can’t be used for virtual host based routing unless the client sends HTTPS requests specifying hostname using the TLS server name, or the request is first decrypted using a default TLS certificate. + +Some legacy TLS clients do not send the server name, so Envoy does not know how to select the right certificate. A fallback certificate is needed for these clients. + +_**Note:** +The minimum TLS protocol version for any fallback request is defined by the `minimum TLS protocol version` set in the Contour configuration file. +Enabling the fallback certificate is not compatible with TLS client authentication._ + +### Fallback Certificate Configuration + +First define the `namespace/name` in the [Contour configuration file][1] of a Kubernetes secret which will be used as the fallback certificate. +Any HTTPProxy which enables fallback certificate delegation must have the fallback certificate delegated to the namespace in which the HTTPProxy object resides. + +To do that, configure `TLSCertificateDelegation` to delegate the fallback certificate to specific or all namespaces (e.g. `*`) which should be allowed to enable the fallback certificate. +Finally, for each root HTTPProxy, set the `Spec.TLS.enableFallbackCertificate` parameter to allow that HTTPProxy to opt-in to the fallback certificate routing. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: fallback-tls-example + namespace: defaultub +spec: + virtualhost: + fqdn: fallback.bar.com + tls: + secretName: testsecret + enableFallbackCertificate: true + routes: + - services: + - name: s1 + port: 80 +--- +apiVersion: projectcontour.io/v1 +kind: TLSCertificateDelegation +metadata: + name: fallback-delegation + namespace: www-admin +spec: + delegations: + - secretName: fallback-secret-name + targetNamespaces: + - "*" +``` + +## Permitting Insecure Requests + +A HTTPProxy can be configured to permit insecure requests to specific Routes. +In this example, any request to `foo2.bar.com/blog` will not receive a 301 redirect to HTTPS, but the `/` route will: + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: tls-example-insecure + namespace: default +spec: + virtualhost: + fqdn: foo2.bar.com + tls: + secretName: testsecret + routes: + - services: + - name: s1 + port: 80 + - conditions: + - prefix: /blog + permitInsecure: true + services: + - name: s2 + port: 80 +``` + +## Client Certificate Validation + +It is possible to protect the backend service from unauthorized external clients by requiring the client to present a valid TLS certificate. +Envoy will validate the client certificate by verifying that it is not expired and that a chain of trust can be established to the configured trusted root CA certificate. +Only those requests with a valid client certificate will be accepted and forwarded to the backend service. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: with-client-auth +spec: + virtualhost: + fqdn: www.example.com + tls: + secretName: secret + clientValidation: + caSecret: client-root-ca + routes: + - services: + - name: s1 + port: 80 +``` + +The preceding example enables validation by setting the optional `clientValidation` attribute. +Its mandatory attribute `caSecret` contains a name of an existing Kubernetes Secret that must be of type "Opaque" and have only a data key named `ca.crt`. +The data value of the key `ca.crt` must be a PEM-encoded certificate bundle and it must contain all the trusted CA certificates that are to be used for validating the client certificate. +If the Opaque Secret also contains one of either `tls.crt` or `tls.key` keys, it will be ignored. + +When using external authorization, it may be desirable to use an external authorization server to validate client certificates on requests, rather than the Envoy proxy. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: with-client-auth-and-ext-authz +spec: + virtualhost: + fqdn: www.example.com + authorization: + # external authorization server configuration + tls: + secretName: secret + clientValidation: + caSecret: client-root-ca + skipClientCertValidation: true + routes: + - services: + - name: s1 + port: 80 +``` + +In the above example, setting the `skipClientCertValidation` field to `true` will configure Envoy to require client certificates on requests and pass them along to a configured authorization server. +Failed validation of client certificates by Envoy will be ignored and the `fail_verify_error` [Listener statistic][2] incremented. +If the `caSecret` field is omitted, Envoy will request but not require client certificates to be present on requests. + +## TLS Session Proxying + +HTTPProxy supports proxying of TLS encapsulated TCP sessions. + +_Note_: The TCP session must be encrypted with TLS. +This is necessary so that Envoy can use SNI to route the incoming request to the correct service. + +If `spec.virtualhost.tls.secretName` is present then that secret will be used to decrypt the TCP traffic at the edge. + +```yaml +# httpproxy-tls-termination.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: example + namespace: default +spec: + virtualhost: + fqdn: tcp.example.com + tls: + secretName: secret + tcpproxy: + services: + - name: tcpservice + port: 8080 + - name: otherservice + port: 9999 + weight: 20 +``` + +The `spec.tcpproxy` key indicates that this _root_ HTTPProxy will forward the de-encrypted TCP traffic to the backend service. + +### TLS Session Passthrough + +If you wish to handle the TLS handshake at the backend service set `spec.virtualhost.tls.passthrough: true` indicates that once SNI demuxing is performed, the encrypted connection will be forwarded to the backend service. +The backend service is expected to have a key which matches the SNI header received at the edge, and be capable of completing the TLS handshake. This is called SSL/TLS Passthrough. + +```yaml +# httpproxy-tls-passthrough.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: example + namespace: default +spec: + virtualhost: + fqdn: tcp.example.com + tls: + passthrough: true + tcpproxy: + services: + - name: tcpservice + port: 8080 + - name: otherservice + port: 9999 + weight: 20 +``` + +[1]: ../configuration#fallback-certificate +[2]: https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/stats#tls-statistics diff --git a/site/content/docs/v1.20.0/config/upstream-tls.md b/site/content/docs/v1.20.0/config/upstream-tls.md new file mode 100644 index 00000000000..72c83062391 --- /dev/null +++ b/site/content/docs/v1.20.0/config/upstream-tls.md @@ -0,0 +1,98 @@ +# Upstream TLS + +A HTTPProxy can proxy to an upstream TLS backend by annotating the upstream Kubernetes Service or by specifying the upstream protocol in the HTTPProxy [services][2] field. +Applying the `projectcontour.io/upstream-protocol.tls` annotation to a Service object tells Contour that TLS should be enabled and which port should be used for the TLS connection. +The same configuration can be specified by setting the protocol name in the `spec.routes.services[].protocol` field on the HTTPProxy object. +If both the annotation and the protocol field are specified, the protocol field takes precedence. +By default, the upstream TLS server certificate will not be validated, but validation can be requested by setting the `spec.routes.services[].validation` field. +This field has mandatory `caSecret` and `subjectName` fields, which specify the trusted root certificates with which to validate the server certificate and the expected server name. +The `caSecret` can be a namespaced name of the form `/`. If the CA secret's namespace is not the same namespace as the `HTTPProxy` resource, [TLS Certificate Delegation][4] must be used to allow the owner of the CA certificate secret to delegate, for the purposes of referencing the CA certificate in a different namespace, permission to Contour to read the Secret object from another namespace. + +_**Note:** +If `spec.routes.services[].validation` is present, `spec.routes.services[].{name,port}` must point to a Service with a matching `projectcontour.io/upstream-protocol.tls` Service annotation._ + +In the example below, the upstream service is named `secure-backend` and uses port `8443`: + +```yaml +# httpproxy-example.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: example +spec: + virtualhost: + fqdn: www.example.com + routes: + - services: + - name: secure-backend + port: 8443 + validation: + caSecret: my-certificate-authority + subjectName: backend.example.com +``` + +```yaml +# service-secure-backend.yaml +apiVersion: v1 +kind: Service +metadata: + name: secure-backend + annotations: + projectcontour.io/upstream-protocol.tls: "8443" +spec: + ports: + - name: https + port: 8443 + selector: + app: secure-backend + +``` + +If the `validation` spec is defined on a service, but the secret which it references does not exist, Contour will reject the update and set the status of the HTTPProxy object accordingly. +This helps prevent the case of proxying to an upstream where validation is requested, but not yet available. + +```yaml +Status: + Current Status: invalid + Description: route "/": service "tls-nginx": upstreamValidation requested but secret not found or misconfigured +``` + +## Upstream Validation + +When defining upstream services on a route, it's possible to configure the connection from Envoy to the backend endpoint to communicate over TLS. +Two configuration items are required, a CA certificate and a `SubjectName` which are both used to verify the backend endpoint's identity. + +The CA certificate bundle for the backend service should be supplied in a Kubernetes Secret. +The referenced Secret must be of type "Opaque" and have a data key named `ca.crt`. +This data value must be a PEM-encoded certificate bundle. + +In addition to the CA certificate and the subject name, the Kubernetes service must also be annotated with a Contour specific annotation: `projectcontour.io/upstream-protocol.tls: ` ([see annotations section][1]). + +_**Note:** This annotation is applied to the Service not the Ingress or HTTPProxy object._ + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: blog + namespace: marketing +spec: + routes: + - services: + - name: s2 + port: 80 + validation: + caSecret: foo-ca-cert + subjectName: foo.marketing +``` + +## Envoy Client Certificate + +Contour can be configured with a `namespace/name` in the [Contour configuration file][3] of a Kubernetes secret which Envoy uses as a client certificate when upstream TLS is configured for the backend. +Envoy will send the certificate during TLS handshake when the backend applications request the client to present its certificate. +Backend applications can validate the certificate to ensure that the connection is coming from Envoy. + +[1]: annotations.md +[2]: api/#projectcontour.io/v1.Service +[3]: ../configuration#fallback-certificate +[4]: tls-delegation.md diff --git a/site/content/docs/v1.20.0/config/virtual-hosts.md b/site/content/docs/v1.20.0/config/virtual-hosts.md new file mode 100644 index 00000000000..d45dda37594 --- /dev/null +++ b/site/content/docs/v1.20.0/config/virtual-hosts.md @@ -0,0 +1,136 @@ +# Virtual Hosts + + +Similar to Ingress, HTTPProxy support name-based virtual hosting. +Name-based virtual hosts use multiple host names with the same IP address. + +``` +foo.bar.com --| |-> foo.bar.com s1:80 + | 178.91.123.132 | +bar.foo.com --| |-> bar.foo.com s2:80 +``` + +Unlike Ingress however, HTTPProxy only support a single root domain per HTTPProxy object. +As an example, this Ingress object: + +```yaml +# ingress-name.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: name-example +spec: + rules: + - host: foo1.bar.com + http: + paths: + - backend: + service: + name: s1 + port: + number: 80 + - host: bar1.bar.com + http: + paths: + - backend: + service: + name: s2 + port: + number: 80 +``` + +must be represented by two different HTTPProxy objects: + +```yaml +# httpproxy-name.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: name-example-foo + namespace: default +spec: + virtualhost: + fqdn: foo1.bar.com + routes: + - services: + - name: s1 + port: 80 +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: name-example-bar + namespace: default +spec: + virtualhost: + fqdn: bar1.bar.com + routes: + - services: + - name: s2 + port: 80 +``` + +A HTTPProxy object that contains a [`virtualhost`][2] field is known as a "root proxy". + +## Virtualhost aliases + +To present the same set of routes under multiple DNS entries (e.g. `www.example.com` and `example.com`), including a service with a `prefix` condition of `/` can be used. + +```yaml +# httpproxy-inclusion-multipleroots.yaml +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: multiple-root + namespace: default +spec: + virtualhost: + fqdn: bar.com + includes: + - name: main + namespace: default +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: multiple-root-www + namespace: default +spec: + virtualhost: + fqdn: www.bar.com + includes: + - name: main + namespace: default +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: main + namespace: default +spec: + routes: + - services: + - name: s2 + port: 80 +``` + +## Restricted root namespaces + +HTTPProxy inclusion allows Administrators to limit which users/namespaces may configure routes for a given domain, but it does not restrict where root HTTPProxies may be created. +Contour has an enforcing mode which accepts a list of namespaces where root HTTPProxy are valid. +Only users permitted to operate in those namespaces can therefore create HTTPProxy with the [`virtualhost`] field ([see API docs][2]). + +This restricted mode is enabled in Contour by specifying a command line flag, `--root-namespaces`, which will restrict Contour to only searching the defined namespaces for root HTTPProxy. This CLI flag accepts a comma separated list of namespaces where HTTPProxy are valid (e.g. `--root-namespaces=default,kube-system,my-admin-namespace`). + +HTTPProxy with a defined [virtualhost][2] field that are not in one of the allowed root namespaces will be flagged as `invalid` and will be ignored by Contour. + +Additionally, when defined, Contour will only watch for Kubernetes secrets in these namespaces ignoring changes in all other namespaces. +Proper RBAC rules should also be created to restrict what namespaces Contour has access matching the namespaces passed to the command line flag. +An example of this is included in the [examples directory][1] and shows how you might create a namespace called `root-httproxy`. + +_**Note:** The restricted root namespace feature is only supported for HTTPProxy CRDs. +`--root-namespaces` does not affect the operation of Ingress objects._ + +[1]: {{< param github_url>}}/tree/{{< param version >}}/examples/root-rbac +[2]: api/#projectcontour.io/v1.VirtualHost diff --git a/site/content/docs/v1.20.0/config/websockets.md b/site/content/docs/v1.20.0/config/websockets.md new file mode 100644 index 00000000000..c0acc70b068 --- /dev/null +++ b/site/content/docs/v1.20.0/config/websockets.md @@ -0,0 +1,25 @@ +# Websockets + +WebSocket support can be enabled on specific routes using the `enableWebsockets` field: + +```yaml +# httpproxy-websockets.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: chat + namespace: default +spec: + virtualhost: + fqdn: chat.example.com + routes: + - services: + - name: chat-app + port: 80 + - conditions: + - prefix: /websocket + enableWebsockets: true # Setting this to true enables websocket for all paths that match /websocket + services: + - name: chat-app + port: 80 +``` diff --git a/site/content/docs/v1.20.0/configuration.md b/site/content/docs/v1.20.0/configuration.md new file mode 100644 index 00000000000..c6d9576036c --- /dev/null +++ b/site/content/docs/v1.20.0/configuration.md @@ -0,0 +1,455 @@ +# Contour Configuration Reference + +- [Serve Flags](#serve-flags) +- [Configuration File](#configuration-file) +- [Environment Variables](#environment-variables) +- [Bootstrap Config File](#bootstrap-config-file) + +## Overview + +There are various ways to configure Contour, flags, the configuration file, as well as environment variables. +Contour has a precedence of configuration for contour serve, meaning anything configured in the config file is overridden by environment vars which are overridden by cli flags. + +## Serve Flags + +The `contour serve` command is the main command which is used to watch for Kubernetes resource and process them into Envoy configuration which is then streamed to any Envoy via its xDS gRPC connection. +There are a number of flags that can be passed to this command which further configures how Contour operates. +Many of these flags are mirrored in the [Contour Configuration File](#configuration-file). + +| Flag Name | Description | +| -------------------------------------------------------- | ---------------------------------------------------------------------- | +| `--config-path` | Path to base configuration | +| `--contour-config-name` | Name of the ContourConfiguration resource to use | +| `--incluster` | Use in cluster configuration | +| `--kubeconfig=` | Path to kubeconfig (if not in running inside a cluster) | +| `--xds-address=` | xDS gRPC API address | +| `--xds-port=` | xDS gRPC API port | +| `--stats-address=` | Envoy /stats interface address | +| `--stats-port=` | Envoy /stats interface port | +| `--debug-http-address=
` | Address the debug http endpoint will bind to. | +| `--debug-http-port=` | Port the debug http endpoint will bind to | +| `--http-address=` | Address the metrics HTTP endpoint will bind to | +| `--http-port=` | Port the metrics HTTP endpoint will bind to. | +| `--health-address=` | Address the health HTTP endpoint will bind to | +| `--health-port=` | Port the health HTTP endpoint will bind to | +| `--contour-cafile=` | CA bundle file name for serving gRPC with TLS | +| `--contour-cert-file=` | Contour certificate file name for serving gRPC over TLS | +| `--contour-key-file=` | Contour key file name for serving gRPC over TLS | +| `--insecure` | Allow serving without TLS secured gRPC | +| `--root-namespaces=` | Restrict contour to searching these namespaces for root ingress routes | +| `--ingress-class-name=` | Contour IngressClass name | +| `--ingress-status-address=
` | Address to set in Ingress object status | +| `--envoy-http-access-log=` | Envoy HTTP access log | +| `--envoy-https-access-log=` | Envoy HTTPS access log | +| `--envoy-service-http-address=` | Kubernetes Service address for HTTP requests | +| `--envoy-service-https-address=` | Kubernetes Service address for HTTPS requests | +| `--envoy-service-http-port=` | Kubernetes Service port for HTTP requests | +| `--envoy-service-https-port=` | Kubernetes Service port for HTTPS requests | +| `--envoy-service-name=` | Name of the Envoy service to inspect for Ingress status details. | +| `--envoy-service-namespace=` | Envoy Service Namespace | +| `--use-proxy-protocol` | Use PROXY protocol for all listeners | +| `--accesslog-format=` | Format for Envoy access logs | +| `--disable-leader-election` | Disable leader election mechanism | +| `--leader-election-lease-duration` | The duration of the leadership lease. | +| `--leader-election-renew-deadline` | The duration leader will retry refreshing leadership before giving up. | +| `--leader-election-retry-period` | The interval which Contour will attempt to acquire leadership lease. | +| `--leader-election-resource-name` | The name of the resource (ConfigMap) leader election will lease. | +| `--leader-election-resource-namespace` | The namespace of the resource (ConfigMap) leader election will lease. | +| `-d, --debug` | Enable debug logging | +| `--kubernetes-debug=` | Enable Kubernetes client debug logging | + +## Configuration File + +A configuration file can be passed to the `--config-path` argument of the `contour serve` command to specify additional configuration to Contour. +In most deployments, this file is passed to Contour via a ConfigMap which is mounted as a volume to the Contour pod. + +The Contour configuration file is optional. +In its absence, Contour will operate with reasonable defaults. +Where Contour settings can also be specified with command-line flags, the command-line value takes precedence over the configuration file. + +| Field Name | Type | Default | Description | +| ------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| accesslog-format | string | `envoy` | This key sets the global [access log format][2] for Envoy. Valid options are `envoy` or `json`. | +| accesslog-format-string | string | None | If present, this specifies custom access log format for Envoy. See [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage) for more information about the syntax. This field only has effect if `accesslog-format` is `envoy` | +| debug | boolean | `false` | Enables debug logging. | +| default-http-versions | string array | HTTP/1.1
HTTP/2 | This array specifies the HTTP versions that Contour should program Envoy to serve. HTTP versions are specified as strings of the form "HTTP/x", where "x" represents the version number. | +| disableAllowChunkedLength | boolean | `false` | If this field is true, Contour will disable the RFC-compliant Envoy behavior to strip the `Content-Length` header if `Transfer-Encoding: chunked` is also set. This is an emergency off-switch to revert back to Envoy's default behavior in case of failures. | +| disablePermitInsecure | boolean | `false` | If this field is true, Contour will ignore `PermitInsecure` field in HTTPProxy documents. | +| envoy-service-name | string | `envoy` | This sets the service name that will be inspected for address details to be applied to Ingress objects. | +| envoy-service-namespace | string | `projectcontour` | This sets the namespace of the service that will be inspected for address details to be applied to Ingress objects. If the `CONTOUR_NAMESPACE` environment variable is present, Contour will populate this field with its value. | +| ingress-status-address | string | None | If present, this specifies the address that will be copied into the Ingress status for each Ingress that Contour manages. It is exclusive with `envoy-service-name` and `envoy-service-namespace`. | +| incluster | boolean | `false` | This field specifies that Contour is running in a Kubernetes cluster and should use the in-cluster client access configuration. | +| json-fields | string array | [fields][5] | This is the list the field names to include in the JSON [access log format][2]. This field only has effect if `accesslog-format` is `json`. | +| kubeconfig | string | `$HOME/.kube/config` | Path to a Kubernetes [kubeconfig file][3] for when Contour is executed outside a cluster. | +| leaderelection | leaderelection | | The [leader election configuration](#leader-election-configuration). | +| policy | PolicyConfig | | The default [policy configuration](#policy-configuration). | +| tls | TLS | | The default [TLS configuration](#tls-configuration). | +| timeouts | TimeoutConfig | | The [timeout configuration](#timeout-configuration). | +| cluster | ClusterConfig | | The [cluster configuration](#cluster-configuration). | +| network | NetworkConfig | | The [network configuration](#network-configuration). | +| listener | ListenerConfig | | The [listener configuration](#listener-configuration). | +| server | ServerConfig | | The [server configuration](#server-configuration) for `contour serve` command. | +| gateway | GatewayConfig | | The [gateway-api Gateway configuration](#gateway-configuration). | +| rateLimitService | RateLimitServiceConfig | | The [rate limit service configuration](#rate-limit-service-configuration). | +| enableExternalNameService | boolean | `false` | Enable ExternalName Service processing. Enabling this has security implications. Please see the [advisory](https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc) for more details. | +| metrics | MetricsParameters | | The [metrics configuration](#metrics-configuration) | + +### TLS Configuration + +The TLS configuration block can be used to configure default values for how +Contour should provision TLS hosts. + +| Field Name | Type | Default | Description | +| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| minimum-protocol-version | string | `1.2` | This field specifies the minimum TLS protocol version that is allowed. Valid options are `1.2` (default) and `1.3`. Any other value defaults to TLS 1.2. | +| fallback-certificate | | | [Fallback certificate configuration](#fallback-certificate). | +| envoy-client-certificate | | | [Client certificate configuration for Envoy](#envoy-client-certificate). | +| cipher-suites | []string | See [config package documentation](https://pkg.go.dev/github.com/projectcontour/contour/pkg/config#pkg-variables) | This field specifies the TLS ciphers to be supported by TLS listeners when negotiating TLS 1.2. This parameter should only be used by advanced users. Note that this is ignored when TLS 1.3 is in use. The set of ciphers that are allowed is a superset of those supported by default in stock, non-FIPS Envoy builds and FIPS builds as specified [here](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/common.proto#envoy-v3-api-field-extensions-transport-sockets-tls-v3-tlsparameters-cipher-suites). Custom ciphers not accepted by Envoy in a standard build are not supported. | + +### Fallback Certificate + +| Field Name | Type | Default | Description | +| ---------- | ------ | ------- | ----------------------------------------------------------------------------------------------- | +| name | string | `""` | This field specifies the name of the Kubernetes secret to use as the fallback certificate. | +| namespace | string | `""` | This field specifies the namespace of the Kubernetes secret to use as the fallback certificate. | + + +### Envoy Client Certificate + +| Field Name | Type | Default | Description | +| ---------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | string | `""` | This field specifies the name of the Kubernetes secret to use as the client certificate and private key when establishing TLS connections to the backend service. | +| namespace | string | `""` | This field specifies the namespace of the Kubernetes secret to use as the client certificate and private key when establishing TLS connections to the backend service. | + +### Leader Election Configuration + +The leader election configuration block configures how a deployment with more than one Contour pod elects a leader. +The Contour leader is responsible for updating the status field on Ingress and HTTPProxy documents. +In the vast majority of deployments, only the `configmap-name` and `configmap-namespace` fields should require any configuration. + +_Note:_ Configuring leader election via the configuration file is deprecated, please use the `contour serve` command line flags instead. + +| Field Name | Type | Default | Description | +| ------------------- | ------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| configmap-name | string | `leader-elect` | The name of the ConfigMap that Contour leader election will lease. | +| configmap-namespace | string | `projectcontour` | The namespace of the ConfigMap that Contour leader election will lease. If the `CONTOUR_NAMESPACE` environment variable is present, Contour will populate this field with its value. | +| lease-duration | [duration][4] | `15s` | The duration of the leadership lease. | +| renew-deadline | [duration][4] | `10s` | The length of time that the leader will retry refreshing leadership before giving up. | +| retry-period | [duration][4] | `2s` | The interval at which Contour will attempt to the acquire leadership lease. | + +### Timeout Configuration + +The timeout configuration block can be used to configure various timeouts for the proxies. All fields are optional; Contour/Envoy defaults apply if a field is not specified. + +| Field Name | Type | Default | Description | +| -------------------------------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| request-timeout | string | none* | This field specifies the default request timeout. Note that this is a timeout for the entire request, not an idle timeout. Must be a [valid Go duration string][4], or omitted or set to `infinity` to disable the timeout entirely. See [the Envoy documentation][12] for more information.

_Note: A value of `0s` previously disabled this timeout entirely. This is no longer the case. Use `infinity` or omit this field to disable the timeout._ | +| connection-idle-timeout | string | `60s` | This field defines how long the proxy should wait while there are no active requests (for HTTP/1.1) or streams (for HTTP/2) before terminating an HTTP connection. The timeout applies to downstream connections only. Must be a [valid Go duration string][4], or `infinity` to disable the timeout entirely. See [the Envoy documentation][8] for more information. | +| stream-idle-timeout | string | `5m`* | This field defines how long the proxy should wait while there is no activity during single request/response (for HTTP/1.1) or stream (for HTTP/2). Timeout will not trigger while HTTP/1.1 connection is idle between two consecutive requests. Must be a [valid Go duration string][4], or `infinity` to disable the timeout entirely. See [the Envoy documentation][9] for more information. | +| max-connection-duration | string | none* | This field defines the maximum period of time after an HTTP connection has been established from the client to the proxy before it is closed by the proxy, regardless of whether there has been activity or not. Must be a [valid Go duration string][4], or omitted or set to `infinity` for no max duration. See [the Envoy documentation][10] for more information. | +| delayed-close-timeout | string | `1s`* | *Note: this is an advanced setting that should not normally need to be tuned.*

This field defines how long envoy will wait, once connection close processing has been initiated, for the downstream peer to close the connection before Envoy closes the socket associated with the connection. Setting this timeout to 'infinity' will disable it. See [the Envoy documentation][13] for more information. | +| connection-shutdown-grace-period | string | `5s`* | This field defines how long the proxy will wait between sending an initial GOAWAY frame and a second, final GOAWAY frame when terminating an HTTP/2 connection. During this grace period, the proxy will continue to respond to new streams. After the final GOAWAY frame has been sent, the proxy will refuse new streams. Must be a [valid Go duration string][4]. See [the Envoy documentation][11] for more information. | + +_This is Envoy's default setting value and is not explicitly configured by Contour._ + +### Cluster Configuration + +The cluster configuration block can be used to configure various parameters for Envoy clusters. + +| Field Name | Type | Default | Description | +| ----------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| dns-lookup-family | string | auto | This field specifies the dns-lookup-family to use for upstream requests to externalName type Kubernetes services from an HTTPProxy route. Values are: `auto`, `v4, `v6` | + +### Network Configuration + +The network configuration block can be used to configure various parameters network connections. + +| Field Name | Type | Default | Description | +| ---------------- | ---- | ------- | ----------------------------------------------------------------------------------------------------------------------- | +| num-trusted-hops | int | 0 | Configures the number of additional ingress proxy hops from the right side of the x-forwarded-for HTTP header to trust. | +| admin-port | int | 9001 | Configures the Envoy Admin read-only listener on Envoy. Set to `0` to disable. | + +### Listener Configuration + +The listener configuration block can be used to configure various parameters for Envoy listener. + +| Field Name | Type | Default | Description | +| ------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| connection-balancer | string | `""` | This field specifies the listener connection balancer. If the value is `exact`, the listener will use the exact connection balancer to balance connections between threads in a single Envoy process. See [the Envoy documentation][14] for more information. | + +### Server Configuration + +The server configuration block can be used to configure various settings for the `contour serve` command. + +| Field Name | Type | Default | Description | +| --------------- | ------ | ------- | ----------------------------------------------------------------------------- | +| xds-server-type | string | contour | This field specifies the xDS Server to use. Options are `contour` or `envoy`. | + +### Gateway Configuration + +The gateway configuration block is used to configure which gateway-api Gateway Contour should configure: + +| Field Name | Type | Default | Description | +| -------------- | ------ | ------- | ------------------------------------------------------------------------------ | +| controllerName | string | | Gateway Class controller name (i.e. projectcontour.io/projectcontour/contour). | + +### Policy Configuration + +The Policy configuration block can be used to configure default policy values +that are set if not overridden by the user. + +The `request-headers` field is used to rewrite headers on a HTTP request, and +the `response-headers` field is used to rewrite headers on a HTTP response. + +| Field Name | Type | Default | Description | +| ---------------- | ------------ | ------- | ------------------------------------------------------------------------------------------------- | +| request-headers | HeaderPolicy | none | The default request headers set or removed on all service routes if not overridden in the object | +| response-headers | HeaderPolicy | none | The default response headers set or removed on all service routes if not overridden in the object | +| applyToIngress | Boolean | false | Whether the global policy should apply to Ingress objects | + +#### HeaderPolicy + +The `set` field sets an HTTP header value, creating it if it doesn't already exist but not overwriting it if it does. +The `remove` field removes an HTTP header. + +| Field Name | Type | Default | Description | +| ---------- | ----------------- | ------- | ------------------------------------------------------------------------------- | +| set | map[string]string | none | Map of headers to set on all service routes if not overridden in the object | +| remove | []string | none | List of headers to remove on all service routes if not overridden in the object | + +Note: the values of entries in the `set` and `remove` fields can be overridden in HTTPProxy objects but it it not possible to remove these entries. + +### Rate Limit Service Configuration + +The rate limit service configuration block is used to configure an optional global rate limit service: + +| Field Name | Type | Default | Description | +| ----------------------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| extensionService | string | | This field identifies the extension service defining the rate limit service, formatted as /. | +| domain | string | contour | This field defines the rate limit domain value to pass to the rate limit service. Acts as a container for a set of rate limit definitions within the RLS. | +| failOpen | bool | false | This field defines whether to allow requests to proceed when the rate limit service fails to respond with a valid rate limit decision within the timeout defined on the extension service. | +| enableXRateLimitHeaders | bool | false | This field defines whether to include the X-RateLimit headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (as defined by the IETF Internet-Draft https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html), on responses to clients when the Rate Limit Service is consulted for a request. | + +### Metrics Configuration + +MetricsParameters holds configurable parameters for Contour and Envoy metrics. +Metrics and health endpoints cannot have same port number when metrics is served over HTTPS. + +| Field Name | Type | Default | Description | +| ----------------------- | ------ | ---------------------------- | -----------------------------------------------------------------------------| +| address | string | 0.0.0.0 | Address that metrics server will bind to. | +| port | int | 8000 (Contour), 8002 (Envoy) | Port that metrics server will bind to. | +| server-certificate-path | string | none | Optional path to the server certificate file. | +| server-key-path | string | none | Optional path to the server private key file. | +| ca-certificate-path | string | none | Optional path to the CA certificate file used to verify client certificates. | + +### Configuration Example + +The following is an example ConfigMap with configuration file included: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: contour + namespace: projectcontour +data: + contour.yaml: | + # + # server: + # determine which XDS Server implementation to utilize in Contour. + # xds-server-type: contour + # + # specify the gateway-api Gateway Contour should configure + # gateway: + # controllerName: projectcontour.io/projectcontour/contour + # + # should contour expect to be running inside a k8s cluster + # incluster: true + # + # path to kubeconfig (if not running inside a k8s cluster) + # kubeconfig: /path/to/.kube/config + # + # Disable RFC-compliant behavior to strip "Content-Length" header if + # "Tranfer-Encoding: chunked" is also set. + # disableAllowChunkedLength: false + # Disable HTTPProxy permitInsecure field + disablePermitInsecure: false + tls: + # minimum TLS version that Contour will negotiate + # minimum-protocol-version: "1.2" + # TLS ciphers to be supported by Envoy TLS listeners when negotiating + # TLS 1.2. + # cipher-suites: + # - '[ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305]' + # - '[ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305]' + # - 'ECDHE-ECDSA-AES256-GCM-SHA384' + # - 'ECDHE-RSA-AES256-GCM-SHA384' + # Defines the Kubernetes name/namespace matching a secret to use + # as the fallback certificate when requests which don't match the + # SNI defined for a vhost. + fallback-certificate: + # name: fallback-secret-name + # namespace: projectcontour + envoy-client-certificate: + # name: envoy-client-cert-secret-name + # namespace: projectcontour + # The following config shows the defaults for the leader election. + # leaderelection: + # configmap-name: leader-elect + # configmap-namespace: projectcontour + ### Logging options + # Default setting + accesslog-format: envoy + # The default access log format is defined by Envoy but it can be customized by setting following variable. + # accesslog-format-string: "...\n" + # To enable JSON logging in Envoy + # accesslog-format: json + # The default fields that will be logged are specified below. + # To customise this list, just add or remove entries. + # The canonical list is available at + # https://godoc.org/github.com/projectcontour/contour/internal/envoy#JSONFields + # json-fields: + # - "@timestamp" + # - "authority" + # - "bytes_received" + # - "bytes_sent" + # - "downstream_local_address" + # - "downstream_remote_address" + # - "duration" + # - "method" + # - "path" + # - "protocol" + # - "request_id" + # - "requested_server_name" + # - "response_code" + # - "response_flags" + # - "uber_trace_id" + # - "upstream_cluster" + # - "upstream_host" + # - "upstream_local_address" + # - "upstream_service_time" + # - "user_agent" + # - "x_forwarded_for" + # + # default-http-versions: + # - "HTTP/2" + # - "HTTP/1.1" + # + # The following shows the default proxy timeout settings. + # timeouts: + # request-timeout: infinity + # connection-idle-timeout: 60s + # stream-idle-timeout: 5m + # max-connection-duration: infinity + # connection-shutdown-grace-period: 5s + # + # Envoy cluster settings. + # cluster: + # configure the cluster dns lookup family + # valid options are: auto (default), v4, v6 + # dns-lookup-family: auto + # + # network: + # Configure the number of additional ingress proxy hops from the + # right side of the x-forwarded-for HTTP header to trust. + # num-trusted-hops: 0 + # Configure the port used to access the Envoy Admin interface. + # admin-port: 9001 + # + # Configure an optional global rate limit service. + # rateLimitService: + # Identifies the extension service defining the rate limit service, + # formatted as /. + # extensionService: projectcontour/ratelimit + # Defines the rate limit domain to pass to the rate limit service. + # Acts as a container for a set of rate limit definitions within + # the RLS. + # domain: contour + # Defines whether to allow requests to proceed when the rate limit + # service fails to respond with a valid rate limit decision within + # the timeout defined on the extension service. + # failOpen: false + # Defines whether to include the X-RateLimit headers X-RateLimit-Limit, + # X-RateLimit-Remaining, and X-RateLimit-Reset (as defined by the IETF + # Internet-Draft linked below), on responses to clients when the Rate + # Limit Service is consulted for a request. + # ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html + # enableXRateLimitHeaders: false + # + # Global Policy settings. + # policy: + # # Default headers to set on all requests (unless set/removed on the HTTPProxy object itself) + # request-headers: + # set: + # # example: the hostname of the Envoy instance that proxied the request + # X-Envoy-Hostname: %HOSTNAME% + # # example: add a l5d-dst-override header to instruct Linkerd what service the request is destined for + # l5d-dst-override: %CONTOUR_SERVICE_NAME%.%CONTOUR_NAMESPACE%.svc.cluster.local:%CONTOUR_SERVICE_PORT% + # # default headers to set on all responses (unless set/removed on the HTTPProxy object itself) + # response-headers: + # set: + # # example: Envoy flags that provide additional details about the response or connection + # X-Envoy-Response-Flags: %RESPONSE_FLAGS% + # Whether or not the policy settings should apply to ingress objects + # applyToIngress: true + # +``` + +_Note:_ The default example `contour` includes this [file][1] for easy deployment of Contour. + +## Environment Variables + +### CONTOUR_NAMESPACE + +If present, the value of the `CONTOUR_NAMESPACE` environment variable is used as: + +1. The value for the `contour bootstrap --namespace` flag unless otherwise specified. +1. The value for the `contour certgen --namespace` flag unless otherwise specified. +1. The value for the `contour serve --envoy-service-namespace` flag unless otherwise specified. +1. The value for the `leaderelection.configmap-namespace` config file setting for `contour serve` unless otherwise specified. + +The `CONTOUR_NAMESPACE` environment variable is set via the [Downward API][6] in the Contour [example manifests][7]. + +## Bootstrap Config File + +The bootstrap configuration file is generated by an initContainer in the Envoy daemonset which runs the `contour bootstrap` command to generate the file. +This configuration file configures the Envoy container to connect to Contour and receive configuration via xDS. + +The next section outlines all the available flags that can be passed to the `contour bootstrap` command which are used to customize +the configuration file to match the environment in which Envoy is deployed. + +### Flags + +There are flags that can be passed to `contour bootstrap` that help configure how Envoy +connects to Contour: + +| Flag | Default | Description | +| -------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| --resources-dir | "" | Directory where resource files will be written. | +| --admin-address | /admin/admin.sock | Path to Envoy admin unix domain socket. | +| --admin-port (Deprecated) | 9001 | Deprecated: Port is now configured as a Contour flag. | +| --xds-address | 127.0.0.1 | Address to connect to Contour xDS server on. | +| --xds-port | 8001 | Port to connect to Contour xDS server on. | +| --envoy-cafile | "" | CA filename for Envoy secure xDS gRPC communication. | +| --envoy-cert-file | "" | Client certificate filename for Envoy secure xDS gRPC communication. | +| --envoy-key-file | "" | Client key filename for Envoy secure xDS gRPC communication. | +| --namespace | projectcontour | Namespace the Envoy container will run, also configured via ENV variable "CONTOUR_NAMESPACE". Namespace is used as part of the metric names on static resources defined in the bootstrap configuration file. | +| --xds-resource-version | v3 | Currently, the only valid xDS API resource version is `v3`. | +| --dns-lookup-family | auto | Defines what DNS Resolution Policy to use for Envoy -> Contour cluster name lookup. Either v4, v6 or auto. | + + +[1]: {{< param github_url>}}/tree/{{< param version >}}/examples/contour/01-contour-config.yaml +[2]: /guides/structured-logs +[3]: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/ +[4]: https://golang.org/pkg/time/#ParseDuration +[5]: https://godoc.org/github.com/projectcontour/contour/internal/envoy#DefaultFields +[6]: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/ +[7]: {{< param github_url>}}/tree/{{< param version >}}/examples/contour +[8]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-httpprotocoloptions-idle-timeout +[9]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-stream-idle-timeout +[10]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-httpprotocoloptions-max-connection-duration +[11]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-drain-timeout +[12]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-request-timeout +[13]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-delayed-close-timeout +[14]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener.proto#config-listener-v3-listener-connectionbalanceconfig diff --git a/site/content/docs/v1.20.0/deploy-options.md b/site/content/docs/v1.20.0/deploy-options.md new file mode 100644 index 00000000000..ac6e9793ae1 --- /dev/null +++ b/site/content/docs/v1.20.0/deploy-options.md @@ -0,0 +1,222 @@ +# Deployment Options + +The [Getting Started][8] guide shows you a simple way to get started with Contour on your cluster. +This topic explains the details and shows you additional options. +Most of this covers running Contour using a Kubernetes Service of `Type: LoadBalancer`. +If you don't have a cluster with that capability see the [Running without a Kubernetes LoadBalancer][1] section. + +## Installation + +### Recommended installation details + +The recommended installation is for Contour to run as a Deployment and Envoy to run as a Daemonset. A secret containing +TLS certificates should be used to secure the gRPC communication between them. A Service of `type: LoadBalancer` should +also be created to forward traffic to the Envoy instances. The [example manifest][2] or [Contour Operator][12] will +create an installation based on these recommendations. + +__Note:__ Contour Operator is alpha and therefore follows the Contour [deprecation policy][13]. + +If you wish to use Host Networking, please see the [appropriate section][3] for the details. + +## Testing your installation + +### Get your hostname or IP address + +To retrieve the IP address or DNS name assigned to your Contour deployment, run: + +```bash +$ kubectl get -n projectcontour service envoy -o wide +``` + +On AWS, for example, the response looks like: + +``` +NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR +contour 10.106.53.14 a47761ccbb9ce11e7b27f023b7e83d33-2036788482.ap-southeast-2.elb.amazonaws.com 80:30274/TCP 3h app=contour +``` + +Depending on your cloud provider, the `EXTERNAL-IP` value is an IP address, or, in the case of Amazon AWS, the DNS name of the ELB created for Contour. Keep a record of this value. + +Note that if you are running an Elastic Load Balancer (ELB) on AWS, you must add more details to your configuration to get the remote address of your incoming connections. +See the [instructions for enabling the PROXY protocol.][4] + +#### Minikube + +On Minikube, to get the IP address of the Contour service run: + +```bash +$ minikube service -n projectcontour envoy --url +``` + +The response is always an IP address, for example `http://192.168.99.100:30588`. This is used as CONTOUR_IP in the rest of the documentation. + +#### kind + +When creating the cluster on Kind, pass a custom configuration to allow Kind to expose port 80/443 to your local host: + +```yaml +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane +- role: worker + extraPortMappings: + - containerPort: 80 + hostPort: 80 + listenAddress: "0.0.0.0" + - containerPort: 443 + hostPort: 443 + listenAddress: "0.0.0.0" +``` + +Then run the create cluster command passing the config file as a parameter. +This file is in the `examples/kind` directory: + +```bash +$ kind create cluster --config examples/kind/kind-expose-port.yaml +``` + +Then, your CONTOUR_IP (as used below) will just be `localhost:80`. + +_Note: We've created a public DNS record (`local.projectcontour.io`) which is configured to resolve to `127.0.0.1``. This allows you to use a real domain name in your kind cluster._ + +### Test with Ingress + +The Contour repository contains an example deployment of the Kubernetes Up and Running demo application, [kuard][5]. +To test your Contour deployment, deploy `kuard` with the following command: + +```bash +$ kubectl apply -f https://projectcontour.io/examples/kuard.yaml +``` + +Then monitor the progress of the deployment with: + +```bash +$ kubectl get po,svc,ing -l app=kuard +``` + +You should see something like: + +``` +NAME READY STATUS RESTARTS AGE +po/kuard-370091993-ps2gf 1/1 Running 0 4m +po/kuard-370091993-r63cm 1/1 Running 0 4m +po/kuard-370091993-t4dqk 1/1 Running 0 4m + +NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE +svc/kuard 10.110.67.121 80/TCP 4m + +NAME HOSTS ADDRESS PORTS AGE +ing/kuard * 10.0.0.47 80 4m +``` + +... showing that there are three Pods, one Service, and one Ingress that is bound to all virtual hosts (`*`). + +In your browser, navigate your browser to the IP or DNS address of the Contour Service to interact with the demo application. + +### Test with HTTPProxy + +To test your Contour deployment with [HTTPProxy][9], run the following command: + +```sh +$ kubectl apply -f https://projectcontour.io/examples/kuard-httpproxy.yaml +``` + +Then monitor the progress of the deployment with: + +```sh +$ kubectl get po,svc,httpproxy -l app=kuard +``` + +You should see something like: + +```sh +NAME READY STATUS RESTARTS AGE +pod/kuard-bcc7bf7df-9hj8d 1/1 Running 0 1h +pod/kuard-bcc7bf7df-bkbr5 1/1 Running 0 1h +pod/kuard-bcc7bf7df-vkbtl 1/1 Running 0 1h + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/kuard ClusterIP 10.102.239.168 80/TCP 1h + +NAME FQDN TLS SECRET FIRST ROUTE STATUS STATUS DESCRIPT +httpproxy.projectcontour.io/kuard kuard.local valid valid HTTPProxy +``` + +... showing that there are three Pods, one Service, and one HTTPProxy . + +In your terminal, use curl with the IP or DNS address of the Contour Service to send a request to the demo application: + +```sh +$ curl -H 'Host: kuard.local' ${CONTOUR_IP} +``` + +## Running without a Kubernetes LoadBalancer + +If you can't or don't want to use a Service of `type: LoadBalancer` there are other ways to run Contour. + +### NodePort Service + +If your cluster doesn't have the capability to configure a Kubernetes LoadBalancer, +or if you want to configure the load balancer outside Kubernetes, +you can change the Envoy Service in the [`02-service-envoy.yaml`][7] file and set `type` to `NodePort`. + +This will have every node in your cluster listen on the resultant port and forward traffic to Contour. +That port can be discovered by taking the second number listed in the `PORT` column when listing the service, for example `30274` in `80:30274/TCP`. + +Now you can point your browser at the specified port on any node in your cluster to communicate with Contour. + +### Host Networking + +You can run Contour without a Kubernetes Service at all. +This is done by having the Envoy pod run with host networking. +Contour's examples utilize this model in the `/examples` directory. +To configure, set: `hostNetwork: true` and `dnsPolicy: ClusterFirstWithHostNet` on your Envoy pod definition. +Next, pass `--envoy-service-http-port=80 --envoy-service-https-port=443` to the contour `serve` command which instructs Envoy to listen directly on port 80/443 on each host that it is running. +This is best paired with a DaemonSet (perhaps paired with Node affinity) to ensure that a single instance of Contour runs on each Node. +See the [AWS NLB tutorial][10] as an example. + +### Upgrading Contour/Envoy + +At times it's needed to upgrade Contour, the version of Envoy, or both. +The included `shutdown-manager` can assist with watching Envoy for open connections while draining and give signal back to Kubernetes as to when it's fine to delete Envoy pods during this process. + +See the [redeploy envoy][11] docs for more information. + +## Running Contour in tandem with another ingress controller + +If you're running multiple ingress controllers, or running on a cloudprovider that natively handles ingress, +you can specify the annotation `kubernetes.io/ingress.class: "contour"` on all ingresses that you would like Contour to claim. +You can customize the class name with the `--ingress-class-name` flag at runtime. +If the `kubernetes.io/ingress.class` annotation is present with a value other than `"contour"`, Contour will ignore that ingress. + +## Uninstall Contour + +To remove Contour from your cluster, delete the namespace: + +```bash +$ kubectl delete ns projectcontour +``` +**Note**: The namespace may differ from above if [Contour Operator][12] was used to +deploy Contour. + +## Uninstall Contour Operator + +To remove Contour Operator from your cluster, delete the operator's namespace: + +```bash +$ kubectl delete ns contour-operator +``` + +[1]: #running-without-a-kubernetes-loadbalancer +[2]: {{< param github_url>}}/tree/{{< param version >}}/examples/contour +[3]: #host-networking +[4]: /guides/proxy-proto.md +[5]: https://github.com/kubernetes-up-and-running/kuard +[7]: {{< param github_url>}}/tree/{{< param version >}}/examples/contour/02-service-envoy.yaml +[8]: /getting-started.md +[9]: config/fundamentals.md +[10]: /guides/deploy-aws-nlb.md +[11]: redeploy-envoy.md +[12]: https://github.com/projectcontour/contour-operator +[13]: https://projectcontour.io/resources/deprecation-policy/ diff --git a/site/content/docs/v1.20.0/github.md b/site/content/docs/v1.20.0/github.md new file mode 100644 index 00000000000..8a0f36b4f4d --- /dev/null +++ b/site/content/docs/v1.20.0/github.md @@ -0,0 +1,80 @@ +This document outlines how we use GitHub. + +## Milestones + +Contour attempts to ship on a quarterly basis. +These releases are tracked with a milestone. +The _current_ release is the milestone with the closest delivery date. + +Issues which are not assigned to the current milestone _should not be worked on_. + +## Priorities + +This project has three levels of priority: + +- p0 - Must fix immediately. +This is reserved for bugs and security issues. A milestone cannot ship with open p0 issues. +- p1 - Should be done. +p1 issues assigned to a milestone _should_ be completed during that milestone. +- p2 - May be done. +p2 issues assigned to a milestone _may_ be completed during that milestone if time permits. + +Issues without a priority are _unprioritised_. Priority will be assigned by a PM or release manager during issue triage. + +## Questions + +We encourage support questions via issues. +Questions will be tagged `question` and are not assigned a milestone or a priority. + +## Waiting for information + +Any issue which lacks sufficient information for triage will be tagged `waiting-for-info`. +Issues with this tag may be closed after a reasonable length of time if further information is not forthcoming. + +## Issue tagging + +Issues without tags have not be triaged. + +During issue triage, usually by a project member, release manager, or pm, one or more tags will be assigned. + +- `Needs-Product` indicates the issue needs attention by a product owner or PM. +- `Needs-design-doc` indicates the issue requires a design document to be circulated. + +These are blocking states, these labels must be resolved, either by PM or agreeing on a design. + +## Assigning an issue + +Issues within a milestone _should_ be assigned to an owner when work commences on them. +Assigning an issue indicates that you are working on it. + +Before you start to work on an issue you should assign yourself. +From that point onward you are responsible for the issue and you are expected to report timely status on the issue to anyone that asks. + +If you cease work on an issue, even if incomplete, you should leave a comment to that effect on the issue and remove yourself as the assignee. +From that point onward you are no longer responsible for the issue, however you may be approached as a subject matter expert--as the last person to touch the issue--by future assignees. + +For infrequent contributors who are not members of the Contour project, assign yourself by leaving a comment to that effect on the issue. + +*Do not hoard issues, you won't enjoy it* + +## Requesting a review + +PRs which are related to issues in the current milestone should be assigned to the current milestone. +This is an indicator to reviewers that the PR is ready for review and should be reviewed in the current milestone. +Occasionally PRs may be assigned to the next milestone indicating they are for review at the start of the next development cycle. + +All PRs should reference the issue they relate to either by one of the following; + +- `Fixes #NNNN` indicating that merging this issue will fix issue #NNNN +- `Updates #NNNN` indicating that merging this issue will progress issue #NNNN to some degree. + +If there is no `Updates` or `Fixes` line in the PR the review will, with the exception of trivial or self evident fixes, be deferred. + +[Further reading][1] + +## Help wanted and good first issues + +The `help wanted` and `good first issue` tags _may_ be assigned to issues _in the current milestone_. +To limit the amount of work in progress, `help wanted` and `good first issue` should not be used for issues outside the current milestone. + +[1]: https://dave.cheney.net/2019/02/18/talk-then-code \ No newline at end of file diff --git a/site/content/docs/v1.20.0/grpc-tls-howto.md b/site/content/docs/v1.20.0/grpc-tls-howto.md new file mode 100644 index 00000000000..2b8e60db982 --- /dev/null +++ b/site/content/docs/v1.20.0/grpc-tls-howto.md @@ -0,0 +1,169 @@ +# Enabling TLS between Envoy and Contour + +This document describes the steps required to secure communication between Envoy and Contour. +The outcome of this is that we will have two Secrets available in the `projectcontour` namespace: + +- **contourcert:** contains Contour's keypair which is used for serving TLS secured gRPC, and the CA's public certificate bundle which is used for validating Envoy's client certificate. +Contour's certificate must be a valid certificate for the name `contour` in order for this to work. +This is currently hardcoded by Contour. +- **envoycert:** contains Envoy's keypair which used as a client for connecting to Contour, and the CA's public certificate bundle which is used for validating Contour's server certificate. + +Note that both Secrets contain a copy of the CA certificate bundle under the `ca.crt` data key. + +## Ways you can get the certificates into your cluster + +- Deploy the Job from [certgen.yaml][1]. +This will run `contour certgen --kube --secrets-format=compact` for you. +- Run `contour certgen --kube` locally. +- Run the manual procedure below. + +## Caveats and warnings + +**Be very careful with your production certificates!** + +This is intended as an example to help you get started. +For any real deployment, you should **carefully** manage all the certificates and control who has access to them. +Make sure you don't commit them to any git repositories either. + +## Manual TLS certificate generation process + +### Generating a CA keypair + +First, we need to generate a keypair: + +``` +$ openssl req -x509 -new -nodes \ + -keyout certs/cakey.pem -sha256 \ + -days 1825 -out certs/cacert.pem \ + -subj "/O=Project Contour/CN=Contour CA" +``` + +Then, the new CA key will be stored in `certs/cakey.pem` and the cert in `certs/cacert.pem`. + +### Generating Contour's keypair + +Next, we need to generate a keypair for Contour. +First, we make a new private key: + +``` +$ openssl genrsa -out certs/contourkey.pem 2048 +``` + +Then, we create a CSR and have our CA sign the CSR and issue a certificate. +This uses the file [certs/cert-contour.ext][2], which ensures that at least one of the valid names of the certificate is the bareword `contour`. +This is required for the handshake to succeed, as `contour bootstrap` configures Envoy to pass this as the SNI server name for the connection. + +``` +$ openssl req -new -key certs/contourkey.pem \ + -out certs/contour.csr \ + -subj "/O=Project Contour/CN=contour" + +$ openssl x509 -req -in certs/contour.csr \ + -CA certs/cacert.pem \ + -CAkey certs/cakey.pem \ + -CAcreateserial \ + -out certs/contourcert.pem \ + -days 1825 -sha256 \ + -extfile certs/cert-contour.ext +``` + +At this point, the contour certificate and key are in the files `certs/contourcert.pem` and `certs/contourkey.pem` respectively. + +### Generating Envoy's keypair + +Next, we generate a keypair for Envoy: + +``` +$ openssl genrsa -out certs/envoykey.pem 2048 +``` + +Then, we generate a CSR and have the CA sign it: + +``` +$ openssl req -new -key certs/envoykey.pem \ + -out certs/envoy.csr \ + -subj "/O=Project Contour/CN=envoy" + +$ openssl x509 -req -in certs/envoy.csr \ + -CA certs/cacert.pem \ + -CAkey certs/cakey.pem \ + -CAcreateserial \ + -out certs/envoycert.pem \ + -days 1825 -sha256 \ + -extfile certs/cert-envoy.ext +``` + +Like the Contour certificate, this CSR uses the file [certs/cert-envoy.ext][3]. +However, in this case, there are no special names required. + +### Putting the certificates in the cluster + +Next, we create the required Secrets in the target Kubernetes cluster: + +```bash +$ kubectl create secret -n projectcontour generic contourcert \ + --from-file=tls.key=./certs/contourkey.pem \ + --from-file=tls.crt=./certs/contourcert.pem \ + --from-file=ca.crt=./certs/cacert.pem \ + --save-config + +$ kubectl create secret -n projectcontour generic envoycert \ + --from-file=tls.key=./certs/envoykey.pem \ + --from-file=tls.crt=./certs/envoycert.pem \ + --from-file=ca.crt=./certs/cacert.pem \ + --save-config +``` + +Note that we don't put the CA **key** into the cluster, there's no reason for that to be there, and that would create a security problem. + +## Rotating Certificates + +Eventually the certificates that Contour and Envoy use will need to be rotated. +The following steps can be taken to replace the certificates that Contour and Envoy are using: + +1. Generate a new keypair for both Contour and Envoy (optionally also for the CA) +2. Update the Secrets that hold the gRPC TLS keypairs +3. Contour and Envoy will automatically rotate their certificates after mounted secrets have been updated by the kubelet + +The secrets can be updated in-place by running: + +```bash +$ kubectl create secret -n projectcontour generic contourcert \ + --from-file=tls.key=./certs/contourkey.pem \ + --from-file=tls.crt=./certs/contourcert.pem \ + --from-file=ca.crt=./certs/cacert.pem \ + --dry-run -o json \ + | kubectl apply -f - + +$ kubectl create secret -n projectcontour generic envoycert \ + --from-file=tls.key=./certs/envoykey.pem \ + --from-file=tls.crt=./certs/envoycert.pem \ + --from-file=ca.crt=./certs/cacert.pem \ + --dry-run -o json \ + | kubectl apply -f - +``` + +There are few preconditions that need to be met before Envoy can automatically reload certificate and key files: + +- Envoy must be version v1.14.1 or later +- The bootstrap configuration must be generated with `contour bootstrap` using the `--resources-dir` argument, see [examples/contour/03-envoy.yaml][4] + +### Rotate using the contour-certgen job + +When using the built-in Contour certificate generation, the following steps can be used: + +1. Delete the contour-certgen job + - `kubectl delete job contour-certgen -n projectcontour` +2. Reapply the contour-certgen job from [certgen.yaml][1] + +## Conclusion + +Once this process is done, the certificates will be present as Secrets in the `projectcontour` namespace, as required by +[examples/contour][5]. + +[1]: {{< param github_url >}}/tree/{{< param version >}}/examples/contour/02-job-certgen.yaml +[2]: {{< param github_url >}}/tree/{{< param version >}}/certs/cert-contour.ext +[3]: {{< param github_url >}}/tree/{{< param version >}}/certs/cert-envoy.ext +[4]: {{< param github_url >}}/tree/{{< param version >}}/examples/contour/03-envoy.yaml +[5]: {{< param github_url >}}/tree/{{< param version >}}/examples/contour + diff --git a/site/content/docs/v1.20.0/img/archoverview.png b/site/content/docs/v1.20.0/img/archoverview.png new file mode 100644 index 0000000000000000000000000000000000000000..f79bbfe1b4b3a7f0de886d7ce059722308be3ab4 GIT binary patch literal 78807 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 RG6D=h;OXk;vd$@?2>^o>9Z3KH literal 0 HcmV?d00001 diff --git a/site/content/docs/v1.20.0/img/contour_deployment_in_k8s.png b/site/content/docs/v1.20.0/img/contour_deployment_in_k8s.png new file mode 100644 index 0000000000000000000000000000000000000000..4fed24d1e232d573cb76e45d13739e6ea7a374cd GIT binary patch literal 77339 zcmd?QbySpp^gk#FiXbop(g@6egp@Q&!w{l?ARyf$F?4rG3@s>KN-53I-7O(Kbc1wB z*FN}>?{D{Iv;iT)N$-XMd9?8xsw6I(_mjl zXxbM)dnb0BnpV#P0fGJ(;Wb4ByhZtmYTP{~i;g#TTFcqE1{|Mz`Z5G9QN^NW88TK{+P z(#J!JVz@)Q#w*>rQB_Iet1yUmiK|Mkc6#tgFw01;YI!gbvyqo5f+Yik( z%AQ8MYeIs9F_Qj!Ffi!79ZBYRk|eXm=min4;mkfIT8A51{06**{`H46E7&}62V*(% z;UZ!%+E}fPh51Nn0gsh3b8$foPu{zh9OXe7dPV+!L4Z}gzdXY&w^px~OQ;%-Wh#lC z*z{+HUoC9@&}31`RW7bB%he1sixx^#&9p7Y(o9D7jTXT$s0zu<$29YTdL88`M$3E2 z$AS@+6QlJicPGmw-fn-FTz@L4gFz` zayX()V! zR_DL&-}OKTSD!iLXjX*x!Yj7(^WP>)jTW0G|E>{-lmFX0#R9cr_cFOSWUK!qb>#b% z3!)S%^?bAU5ueJY^HMB`T`Wd_#PH<28}ipI3E;{1+*&_<`lk8h`QWqf{o0l#D}|+| zcCq8NZMkrH7!8{?a-t$zIXjIyUGuwfxjC%U0Y3fAHjn%&1w5)Pi?W`(7m!e)0PAsOwO2PQ!-woKP=}L9YII$V1i(xTm~0Bxc7&bYc3^` zGBdyg3I3}!=$cx5iYeDhe$o8m3QCtN###OP^p)zVTA4HGMTRJ}S?1&B6mQKlEW6^! zdVc5D*XIl0gLvPMDv@}=C+%{3;jsvr_2H~<{o^rM2>PhO=f5M!j)o9q!o-E*{@AL8 zjywYP72%GJ@>}^1CDjA@{+&|)0!~NNQ*&epv*%E?xEPkCa=`hdA>$WjLKMl39k5IO zT7$lKd#V6=xIWm-FhIp+>f>>~p`2K!=l?Ex$~Yv8YI>r;&W?ID#)RJ@&FZ6cUumIN z%H~?KC{fR)6WmIAr!F�(5=bALwvO{nNp0g9L(R9(1Z2M)fXqgKB~;n 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 C*@B<| literal 0 HcmV?d00001 diff --git a/site/content/docs/v1.20.0/img/shutdownmanager.png b/site/content/docs/v1.20.0/img/shutdownmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..ab8b7821d3cae1c3fe095e22d5c0595e0a1eeda0 GIT binary patch literal 51051 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 As{jB1 literal 0 HcmV?d00001 diff --git a/site/content/docs/v1.20.0/img/source/shutdownmanager.drawio b/site/content/docs/v1.20.0/img/source/shutdownmanager.drawio new file mode 100644 index 00000000000..99b86620c42 --- /dev/null +++ b/site/content/docs/v1.20.0/img/source/shutdownmanager.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/site/content/docs/v1.20.0/redeploy-envoy.md b/site/content/docs/v1.20.0/redeploy-envoy.md new file mode 100644 index 00000000000..69e25340c75 --- /dev/null +++ b/site/content/docs/v1.20.0/redeploy-envoy.md @@ -0,0 +1,81 @@ +# Redeploying Envoy + +The Envoy process, the data path component of Contour, at times needs to be re-deployed. +This could be due to an upgrade, a change in configuration, or a node-failure forcing a redeployment. + +When implementing this roll out, the following steps should be taken: + +1. Stop Envoy from accepting new connections +2. Start draining existing connections in Envoy by sending a `POST` request to `/healthcheck/fail` endpoint +3. Wait for connections to drain before allowing Kubernetes to `SIGTERM` the pod + +## Overview + +Contour implements an `envoy` sub-command named `shutdown-manager` whose job is to manage a single Envoy instances lifecycle for Kubernetes. +The `shutdown-manager` runs as a new container alongside the Envoy container in the same pod. +It exposes two HTTP endpoints which are used for `livenessProbe` as well as to handle the Kubernetes `preStop` event hook. + +- **livenessProbe**: This is used to validate the shutdown manager is still running properly. If requests to `/healthz` fail, the container will be restarted. +- **preStop**: This is used to keep the Envoy container running while waiting for connections to drain. The `/shutdown` endpoint blocks until the connections are drained. + +```yaml + - name: shutdown-manager + command: + - /bin/contour + args: + - envoy + - shutdown-manager + image: ghcr.io/projectcontour/contour:main + imagePullPolicy: Always + lifecycle: + preStop: + exec: + command: + - /bin/contour + - envoy + - shutdown + livenessProbe: + httpGet: + path: /healthz + port: 8090 + initialDelaySeconds: 3 + periodSeconds: 10 +``` + +The Envoy container also has some configuration to implement the shutdown manager. +First the `preStop` hook is configured to use the `/shutdown` endpoint which blocks the Envoy container from exiting. +Finally, the pod's `terminationGracePeriodSeconds` is customized to extend the time in which Kubernetes will allow the pod to be in the `Terminating` state. +The termination grace period defines an upper bound for long-lived sessions. +If during shutdown, the connections aren't drained to the configured amount, the `terminationGracePeriodSeconds` will send a `SIGTERM` to the pod killing it. + +![shutdown-manager overview][1] + +### Shutdown Manager Config Options + +The `shutdown-manager` runs as another container in the Envoy pod. +When the pod is requested to terminate, the `preStop` hook on the `shutdown-manager` executes the `contour envoy shutdown` command initiating the shutdown sequence. + +The shutdown manager has a single argument that can be passed to change how it behaves: + +| Name | Type | Default | Description | +|------------|------|---------|-------------| +| serve-port | integer | 8090 | Port to serve the http server on | +| ready-file | string | /admin/ok | File to poll while waiting shutdown to be completed. | + +### Shutdown Config Options + +The `shutdown` command does the work of draining connections from Envoy and polling for open connections. + +The shutdown command has a few arguments that can be passed to change how it behaves: + +| Name | Type | Default | Description | +|------------|------|---------|-------------| +| check-interval | duration | 5s | Time interval to poll Envoy for open connections. | +| check-delay | duration | 60s | Time wait before polling Envoy for open connections. | +| drain-delay | duration | 60s | Time wait before draining Envoy connections. | +| min-open-connections | integer | 0 | Min number of open connections when polling Envoy. | +| admin-port (Deprecated) | integer | 9001 | Deprecated: No longer used, Envoy admin interface runs as a unix socket. | +| admin-address | string | /admin/admin.sock | Path to Envoy admin unix domain socket. | +| ready-file | string | /admin/ok | File to write when shutdown is completed. | + + [1]: ../img/shutdownmanager.png diff --git a/site/content/docs/v1.20.0/start-contributing.md b/site/content/docs/v1.20.0/start-contributing.md new file mode 100644 index 00000000000..9cd4dfca82d --- /dev/null +++ b/site/content/docs/v1.20.0/start-contributing.md @@ -0,0 +1,139 @@ +# Getting Started with Contributing + +Thanks for your interest in contributing to Contour. Community contributions are always needed, welcome, and appreciated. This guide shows how you can contribute to Contour in the following areas: + +- Code +- Website +- Documentation + +Please familiarize yourself with the [Code of Conduct][1] and project [Philosophy][15] before contributing. + +# Getting Started with Code + +Everything is managed on the [Project Contour GitHub][2] organization. Create an issue for a new idea or look for issues labeled **good first issue** to get started. + +## How we work + +See [How We Work][3] for an overview: +- Issue management +- Code reviews +- Coding practice +- GitHub labels + +## Contribution workflow + +Review the [Contribution workflow][4] to understand how to work with the code. + +Below is a list of workflow areas: +- Building from source +- Contribution workflow +- Contour testing +- Developer Certificate of Origin (DCO) sign off + +# Getting Started with the Website + +Updates, corrections, or improvements are managed through [GitHub][16] issues. + +When you are ready to take on an issue, see [Website Contribution Guidelines][5] to understand how the Contour website contributions are managed. There is information on: +- Site structure +- Link formatting +- Testing +- Setting up your environment + +# Getting Started with Documentation + +Documentation is critical to the success of any project. Open to all levels, Contour needs help to create and update its documentation. Join an [office hours][9] meeting and learn more about the Tech Docs Working Group. + +Review the [Contour Technical Documentation Contributing Guide][6] for instructions to set up your environment. + +Technical documentation will follow the [Website Contribution Guidelines][5]. + +## New documentation suggestions + +If you have a document suggestion, create an issue in [GitHub][16]. The team will triage and prioritize the issue. Connect on Slack or in a meeting to discuss your issue or request. + +## Helping with identified document issues + +Take a look at the project issues list with the label **area/documentation**. If you are new to technical writing, add in the **good first issue** label: +[area/documentation and good first issue][7] + +Reach out on Slack or a Contour meeting for any assistance. Help is always appreciated. + +# Filing and Working on Issues + +Whether code, website, or documentation, Contour uses GitHub to create, track, and manage all issues. + +If there is a fix or a suggestion for improvement, create an issue in [GitHub][16]. + +All issues are reviewed and evaluated by the Contour team. + +# Meet the Community and the Team + +To find out more about contributing to Contour, connect with us at a Contour Community Meeting, on Slack, or through the mailing list. We also have an Office Hours meeting to answer “How do I…” questions. + +## Contour Community meetings + +Discuss issues, features, or suggestions with the Contour team and other community members. + +See the [Community][8] page for: +- Meeting schedule +- Meeting notes with zoom link +- Meeting recordings + +## Contour Office Hours meetings + +Ask anything during office hours and find out more about Contour. + +Ask questions: +- “How do I do this in Contour?” +- “Why does Contour do this thing this way?” +- “Where can I find…?” + +See the [Office Hours][9] page for: +- Meeting schedule +- Meeting link +- Meeting recordings + +## Mailing list + +To get email updates to Contour, join the [mailing list][10]. Topics include: +- Release notifications +- Issues +- Feedback and suggestions +- Meeting notifications + +## Find us +There are many ways to connect with the Contour team: + +- Slack: Kubernetes [#contour][11] +- Contour YouTube Channel: [CNCF Contour][12] +- Twitter: [@projectcontour][13] +- GitHub: [projectcontour][14] + +# Want More Contributing Information? + +Slack or a meeting is a great way to introduce yourself. Let us know what you are interested in, your background, and what you want to accomplish. + +# Next steps + +Come out and join a [Community meeting][8] or an [Office Hours meeting][9]. Ask questions about how to get started or just sit back and get to know the team. + + + +[1]: {{< param github_url >}}/blob/main/CODE_OF_CONDUCT.md +[2]: https://github.com/projectcontour +[3]: {{< relref "resources/how-we-work.md" >}} +[4]: {{< param github_url >}}/blob/main/CONTRIBUTING.md +[5]: {{< param github_url >}}/blob/main/SITE_CONTRIBUTION.md +[6]: {{< relref "resources/contributing-docs.md" >}} +[7]: {{< param github_url >}}/issues/?q=is%3Aopen+is%3Aissue+label%3Aarea%2Fdocumentation+label%3A%22good+first+issue%22 +[8]: {{< relref "community.md" >}} +[9]: https://github.com/projectcontour/community/wiki/Office-Hours +[10]: https://lists.cncf.io/g/cncf-contour-users/ +[11]: {{< param slack_url >}} +[12]: https://www.youtube.com/channel/UCCde7QSfcyYJ8AuXofD5bTA +[13]: https://twitter.com/projectcontour +[14]: https://github.com/projectcontour +[15]: {{< relref "resources/philosophy.md" >}} +[16]: {{< param github_url >}}/issues/ +[17]: {{< param github_url >}}/ \ No newline at end of file diff --git a/site/content/docs/v1.20.0/troubleshooting.md b/site/content/docs/v1.20.0/troubleshooting.md new file mode 100644 index 00000000000..57c73dc9843 --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting.md @@ -0,0 +1,38 @@ +## Troubleshooting + +If you encounter issues, follow the guides below for help. For topics not covered here, you can [file an issue][0], or talk to us on the [#contour channel][1] on Kubernetes Slack. + +### [Envoy Administration Access][2] +Review the linked steps to learn how to access the administration interface for your Envoy instance. + +### [Contour Debug Logging][3] +Learn how to enable debug logging to diagnose issues between Contour and the Kubernetes API. + +### [Envoy Debug Logging][4] +Learn how to enable debug logging to diagnose TLS connection issues. + +### [Visualize the Contour Graph][5] +Learn how to visualize Contour's internal object graph in [DOT][9] format, or as a png file. + +### [Show Contour xDS Resources][6] +Review the linked steps to view the [xDS][10] resource data exchanged by Contour and Envoy. + +### [Profiling Contour][7] +Learn how to profile Contour by using [net/http/pprof][11] handlers. + +### [Contour Operator][8] +Follow the linked guide to learn how to troubleshoot issues with [Contour Operator][12]. + +[0]: {{< param github_url >}}/issues +[1]: {{< param slack_url >}} +[2]: /docs/{{< param latest_version >}}/troubleshooting/envoy-admin-interface/ +[3]: /docs/{{< param latest_version >}}/troubleshooting/contour-debug-log/ +[4]: /docs/{{< param latest_version >}}/troubleshooting/envoy-debug-log/ +[5]: /docs/{{< param latest_version >}}/troubleshooting/contour-graph/ +[6]: /docs/{{< param latest_version >}}/troubleshooting/contour-xds-resources/ +[7]: /docs/{{< param latest_version >}}/troubleshooting/profiling-contour/ +[8]: /docs/{{< param latest_version >}}/troubleshooting/operator/ +[9]: https://en.wikipedia.org/wiki/Dot +[10]: https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol +[11]: https://golang.org/pkg/net/http/pprof/ +[12]: https://github.com/projectcontour/contour-operator diff --git a/site/content/docs/v1.20.0/troubleshooting/contour-debug-log.md b/site/content/docs/v1.20.0/troubleshooting/contour-debug-log.md new file mode 100644 index 00000000000..821634242c6 --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting/contour-debug-log.md @@ -0,0 +1,6 @@ +# Enabling Contour Debug Logging + +The `contour serve` subcommand has two command-line flags that can be helpful for debugging. +The `--debug` flag enables general Contour debug logging, which logs more information about how Contour is processing API resources. +The `--kubernetes-debug` flag enables verbose logging in the Kubernetes client API, which can help debug interactions between Contour and the Kubernetes API server. +This flag requires an integer log level argument, where higher number indicates more detailed logging. diff --git a/site/content/docs/v1.20.0/troubleshooting/contour-graph.md b/site/content/docs/v1.20.0/troubleshooting/contour-graph.md new file mode 100644 index 00000000000..5abcfeb22af --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting/contour-graph.md @@ -0,0 +1,25 @@ +# Visualizing Contour's Internal Object Graph + +Contour models its configuration using a directed acyclic graph (DAG) of internal objects. +This can be visualized through a debug endpoint that outputs the DAG in [DOT][2] format. +To visualize the graph, you must have [`graphviz`][3] installed on your system. + +To download the graph and save it as a PNG: + +```bash +# Port forward into the contour pod +$ CONTOUR_POD=$(kubectl -n projectcontour get pod -l app=contour -o name | head -1) +# Do the port forward to that pod +$ kubectl -n projectcontour port-forward $CONTOUR_POD 6060 +# Download and store the DAG in png format +$ curl localhost:6060/debug/dag | dot -T png > contour-dag.png +``` + +The following is an example of a DAG that maps `http://kuard.local:80/` to the +`kuard` service in the `default` namespace: + +![Sample DAG][4] + +[2]: https://en.wikipedia.org/wiki/DOT +[3]: https://graphviz.gitlab.io/ +[4]: /img/kuard-dag.png diff --git a/site/content/docs/v1.20.0/troubleshooting/contour-xds-resources.md b/site/content/docs/v1.20.0/troubleshooting/contour-xds-resources.md new file mode 100644 index 00000000000..69f413a8cb3 --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting/contour-xds-resources.md @@ -0,0 +1,19 @@ +# Interrogate Contour's xDS Resources + +Sometimes it's helpful to be able to interrogate Contour to find out exactly what [xDS][1] resource data it is sending to Envoy. +Contour ships with a `contour cli` subcommand which can be used for this purpose. + +Because Contour secures its communications with Envoy using Secrets in the cluster, the easiest way is to run `contour cli` commands _inside_ the pod. +Do this is via `kubectl exec`: + +```bash +# Get one of the pods that matches the examples/daemonset +$ CONTOUR_POD=$(kubectl -n projectcontour get pod -l app=contour -o jsonpath='{.items[0].metadata.name}') +# Do the port forward to that pod +$ kubectl -n projectcontour exec $CONTOUR_POD -c contour -- contour cli lds --cafile=/certs/ca.crt --cert-file=/certs/tls.crt --key-file=/certs/tls.key +``` + +Which will stream changes to the LDS api endpoint to your terminal. +Replace `contour cli lds` with `contour cli rds` for route resources, `contour cli cds` for cluster resources, and `contour cli eds` for endpoints. + +[1]: https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol diff --git a/site/content/docs/v1.20.0/troubleshooting/envoy-admin-interface.md b/site/content/docs/v1.20.0/troubleshooting/envoy-admin-interface.md new file mode 100644 index 00000000000..c44b6fe3e9d --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting/envoy-admin-interface.md @@ -0,0 +1,32 @@ +# Accessing the Envoy Administration Interface + +Getting access to the Envoy [administration interface][1] can be useful for diagnosing issues with routing or cluster health. +However, Contour doesn't expose the entire Envoy Administration interface since that interface contains many options, such as shutting down Envoy or draining traffic. +To prohibit this behavior, Contour only exposes the read-only options from the admin interface which still allows for debugging Envoy, but without the options mentioned previously. + +Those endpoints are: + - /certs + - /clusters + - /listeners + - /config_dump + - /memory + - /ready + - /runtime + - /server_info + - /stats + - /stats/prometheus + - /stats/recentlookups + +The Envoy administration interface is bound by default to `http://127.0.0.1:9001`. +To access it from your workstation use `kubectl port-forward` like so: + +```sh +# Get one of the pods that matches the Envoy daemonset +ENVOY_POD=$(kubectl -n projectcontour get pod -l app=envoy -o name | head -1) +# Do the port forward to that pod +kubectl -n projectcontour port-forward $ENVOY_POD 9001 +``` + +Then navigate to `http://127.0.0.1:9001/` to access the administration interface for the Envoy container running on that pod. + +[1]: https://www.envoyproxy.io/docs/envoy/latest/operations/admin diff --git a/site/content/docs/v1.20.0/troubleshooting/envoy-debug-log.md b/site/content/docs/v1.20.0/troubleshooting/envoy-debug-log.md new file mode 100644 index 00000000000..bfef4fa5531 --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting/envoy-debug-log.md @@ -0,0 +1,8 @@ +# Enabling Envoy Debug Logging + +The `envoy` command has a `--log-level` [flag][1] that can be useful for debugging. +By default, it's set to `info`. +To change it to `debug`, edit the `envoy` DaemonSet in the `projectcontour` namespace and replace the `--log-level info` flag with `--log-level debug`. +Setting the Envoy log level to `debug` can be particilarly useful for debugging TLS connection failures. + +[1]: https://www.envoyproxy.io/docs/envoy/latest/operations/cli diff --git a/site/content/docs/v1.20.0/troubleshooting/operator.md b/site/content/docs/v1.20.0/troubleshooting/operator.md new file mode 100644 index 00000000000..0d34d20f2f8 --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting/operator.md @@ -0,0 +1,91 @@ +# Operator Troubleshooting + +[Contour Operator][1] runs in a Kubernetes cluster and is managed by a +[Deployment][2] resource. The following steps can be used to verify and +troubleshoot the operator: + +Verify the operator deployment is available: +```bash +$ kubectl get deploy/contour-operator -n contour-operator +NAME READY UP-TO-DATE AVAILABLE AGE +contour-operator 1/1 1 1 39m +``` + +Check the logs of the operator: +```bash +$ kubectl logs deploy/contour-operator -n contour-operator -c contour-operator -f +2020-12-01T00:10:14.245Z INFO controller-runtime.metrics metrics server is starting to listen {"addr": "127.0.0.1:8080"} +2020-12-01T00:10:14.341Z INFO setup starting contour-operator +I1201 00:10:14.343439 1 leaderelection.go:243] attempting to acquire leader lease contour-operator/0d879e31.projectcontour.io... +2020-12-01T00:10:14.345Z INFO controller-runtime.manager starting metrics server {"path": "/metrics"} +I1201 00:10:14.442755 1 leaderelection.go:253] successfully acquired lease contour-operator/0d879e31.projectcontour.io +2020-12-01T00:10:14.447Z INFO controller Starting EventSource {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "source": "kind source: /, Kind="} +2020-12-01T00:10:14.540Z DEBUG controller-runtime.manager.events Normal {"object": {"kind":"ConfigMap","namespace":"contour-operator","name":"0d879e31.projectcontour.io","uid":"40ebcf47-a105-4efc-b7e9-993df2070a07","apiVersion":"v1","resourceVersion":"33899"}, "reason": "LeaderElection", "message": "contour-operator-55d6c7b4b5-hkd78_e960056c-f959-45c9-8686-9b85e09e21d8 became leader"} +2020-12-01T00:10:14.842Z INFO controller Starting EventSource {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "source": "kind source: /, Kind="} +2020-12-01T00:10:15.046Z INFO controller Starting EventSource {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "source": "kind source: /, Kind="} +2020-12-01T00:10:15.340Z INFO controller Starting Controller {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour"} +2020-12-01T00:10:15.341Z INFO controller Starting workers {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "worker count": 1} +... +``` + +When a `Contour` is created, the operator should successfully reconcile the object: +```bash +... +2020-12-01T00:10:15.341Z INFO controllers.Contour reconciling {"request": "default/contour-sample"} +... +2020-12-01T00:10:15.442Z DEBUG controller Successfully Reconciled {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "name": "contour-sample", "namespace": "default"} +``` + +Check the status of your `Contour` resource: +```bash +$ kubectl get contour/contour-sample -o yaml +apiVersion: operator.projectcontour.io/v1alpha1 +kind: Contour +metadata: + name: contour-sample + namespace: default +... +status: + availableContours: 2 + availableEnvoys: 1 + conditions: + - lastTransitionTime: "2020-12-01T00:55:38Z" + message: Contour has minimum availability. + reason: ContourAvailable + status: "True" + type: Available +``` + +If the `Contour` does not become available, check the status of operands. +```bash +$ kubectl get po -n projectcontour +NAME READY STATUS RESTARTS AGE +contour-7649c6f6cc-9hxn9 1/1 Running 0 6m18s +contour-7649c6f6cc-sb4nn 1/1 Running 0 6m18s +contour-certgen-vcsqd 0/1 Completed 0 6m18s +envoy-qshxf 2/2 Running 0 6m18s +``` + +Check the logs of the operands. The following example checks the logs of the +contour deployment operand: +```bash +$ kubectl logs deploy/contour -n projectcontour -c contour -f +2020-12-01T00:10:14.245Z INFO controller-runtime.metrics metrics server is starting to listen {"addr": "127.0.0.1:8080"} +2020-12-01T00:10:14.341Z INFO setup starting contour-operator +I1201 00:10:14.343439 1 leaderelection.go:243] attempting to acquire leader lease contour-operator/0d879e31.projectcontour.io... +2020-12-01T00:10:14.345Z INFO controller-runtime.manager starting metrics server {"path": "/metrics"} +I1201 00:10:14.442755 1 leaderelection.go:253] successfully acquired lease contour-operator/0d879e31.projectcontour.io +2020-12-01T00:10:14.447Z INFO controller Starting EventSource {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "source": "kind source: /, Kind="} +2020-12-01T00:10:14.540Z DEBUG controller-runtime.manager.events Normal {"object": {"kind":"ConfigMap","namespace":"contour-operator","name":"0d879e31.projectcontour.io","uid":"40ebcf47-a105-4efc-b7e9-993df2070a07","apiVersion":"v1","resourceVersion":"33899"}, "reason": "LeaderElection", "message": "contour-operator-55d6c7b4b5-hkd78_e960056c-f959-45c9-8686-9b85e09e21d8 became leader"} +2020-12-01T00:10:14.842Z INFO controller Starting EventSource {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "source": "kind source: /, Kind="} +2020-12-01T00:10:15.046Z INFO controller Starting EventSource {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "source": "kind source: /, Kind="} +2020-12-01T00:10:15.340Z INFO controller Starting Controller {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour"} +2020-12-01T00:10:15.341Z INFO controller Starting workers {"reconcilerGroup": "operator.projectcontour.io", "reconcilerKind": "Contour", "controller": "contour", "worker count": 1} +... +``` + +__Note:__ `projectcontour` is the default namespace used by operands of a `Contour` +when `spec.namespace.name` is unspecified. + +[1]: https://github.com/projectcontour/contour-operator +[2]: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ diff --git a/site/content/docs/v1.20.0/troubleshooting/profiling-contour.md b/site/content/docs/v1.20.0/troubleshooting/profiling-contour.md new file mode 100644 index 00000000000..95bb0164210 --- /dev/null +++ b/site/content/docs/v1.20.0/troubleshooting/profiling-contour.md @@ -0,0 +1,14 @@ +# Accessing Contour's /debug/pprof Service + +Contour exposes the [net/http/pprof][1] handlers for `go tool pprof` and `go tool trace` by default on `127.0.0.1:6060`. +This service is useful for profiling Contour. +To access it from your workstation use `kubectl port-forward` like so, + +```bash +# Get one of the pods that matches the Contour deployment +$ CONTOUR_POD=$(kubectl -n projectcontour get pod -l app=contour -o name | head -1) +# Do the port forward to that pod +$ kubectl -n projectcontour port-forward $CONTOUR_POD 6060 +``` + +[1]: https://golang.org/pkg/net/http/pprof diff --git a/site/content/resources/compatibility-matrix.md b/site/content/resources/compatibility-matrix.md index c143d30241f..d1abec4e97d 100644 --- a/site/content/resources/compatibility-matrix.md +++ b/site/content/resources/compatibility-matrix.md @@ -11,24 +11,25 @@ These combinations of versions are specifically tested in CI and supported by th | Contour Version | Envoy Version | Kubernetes Versions | Operator Version | Gateway API Version | | --------------- | :------------------- | ------------------- | ---------------- | --------------------| | main | [1.21.0][14] | 1.23, 1.22, 1.21 | [main][50] | v1alpha2 | -| 1.19.1 | [1.19.1][13] | 1.22, 1.21, 1.20 | [1.19.1][65] | v1alpha1 | -| 1.19.0 | [1.19.1][13] | 1.22, 1.21, 1.20 | [1.19.0][64] | v1alpha1 | +| 1.20.0 | [1.21.0][14] | 1.23, 1.22, 1.21 | [1.20.0][67] | v1alpha2 | +| 1.19.1 | [1.19.1][13] | 1.22, 1.21, 1.20 | [1.19.1][65] | v1alpha1 | +| 1.19.0 | [1.19.1][13] | 1.22, 1.21, 1.20 | [1.19.0][64] | v1alpha1 | | 1.18.3 | [1.19.1][13] | 1.21, 1.20, 1.19 | [1.18.3][66] | v1alpha1 | | 1.18.2 | [1.19.1][13] | 1.21, 1.20, 1.19 | [1.18.2][63] | v1alpha1 | | 1.18.1 | [1.19.1][13] | 1.21, 1.20, 1.19 | [1.18.1][62] | v1alpha1 | | 1.18.0 | [1.19.0][10] | 1.21, 1.20, 1.19 | [1.18.0][61] | v1alpha1 | | 1.17.2 | [1.18.4][12] | 1.21, 1.20, 1.19 | N/A | v1alpha1 | -| 1.17.1 | [1.18.3][9] | 1.21, 1.20, 1.19 | N/A | v1alpha1 | -| 1.17.0 | [1.18.3][9] | 1.21, 1.20, 1.19 | [1.17.0][60] | v1alpha1 | +| 1.17.1 | [1.18.3][9] | 1.21, 1.20, 1.19 | N/A | v1alpha1 | +| 1.17.0 | [1.18.3][9] | 1.21, 1.20, 1.19 | [1.17.0][60] | v1alpha1 | | 1.16.1 | [1.18.4][12] | 1.21, 1.20, 1.19 | N/A | v1alpha1 | -| 1.16.0 | [1.18.3][9] | 1.21, 1.20, 1.19 | [1.16.0][59] | v1alpha1 | +| 1.16.0 | [1.18.3][9] | 1.21, 1.20, 1.19 | [1.16.0][59] | v1alpha1 | | 1.15.2 | [1.18.4][12] | 1.21, 1.20, 1.19 | N/A | v1alpha1 | -| 1.15.1 | [1.18.3][9] | 1.21, 1.20, 1.19 | [1.15.1][58] | v1alpha1 | -| 1.15.0 | [1.18.2][8] | 1.21, 1.20, 1.19 | [1.15.0][57] | v1alpha1 | +| 1.15.1 | [1.18.3][9] | 1.21, 1.20, 1.19 | [1.15.1][58] | v1alpha1 | +| 1.15.0 | [1.18.2][8] | 1.21, 1.20, 1.19 | [1.15.0][57] | v1alpha1 | | 1.14.2 | [1.17.4][11] | 1.20, 1.19, 1.18 | N/A | v1alpha1 | -| 1.14.1 | [1.17.2][7] | 1.20, 1.19, 1.18 | [1.14.1][56] | v1alpha1 | -| 1.14.0 | [1.17.1][6] | 1.20, 1.19, 1.18 | [1.14.0][55] | v1alpha1 | -| 1.13.1 | [1.17.1][6] | 1.20, 1.19, 1.18 | [1.13.1][54] | v1alpha1 | +| 1.14.1 | [1.17.2][7] | 1.20, 1.19, 1.18 | [1.14.1][56] | v1alpha1 | +| 1.14.0 | [1.17.1][6] | 1.20, 1.19, 1.18 | [1.14.0][55] | v1alpha1 | +| 1.13.1 | [1.17.1][6] | 1.20, 1.19, 1.18 | [1.13.1][54] | v1alpha1 | | 1.13.0 | [1.17.0][5] | 1.20, 1.19, 1.18 | [1.13.0][53] | v1alpha1 | | 1.12.0 | [1.17.0][5] | 1.19, 1.18, 1.17 | [1.12.0][52] | N/A | | 1.11.0 | [1.16.2][4] | 1.19, 1.18, 1.17 | [1.11.0][51] | N/A | @@ -126,6 +127,7 @@ __Note:__ This list of extensions was last verified to be complete with Envoy v1 [64]: https://github.com/projectcontour/contour-operator/releases/tag/v1.19.0 [65]: https://github.com/projectcontour/contour-operator/releases/tag/v1.19.1 [66]: https://github.com/projectcontour/contour-operator/releases/tag/v1.18.3 +[67]: https://github.com/projectcontour/contour-operator/releases/tag/v1.20.0 [98]: https://github.com/kubernetes/client-go [99]: https://github.com/kubernetes/client-go#compatibility-matrix diff --git a/site/content/resources/upgrading.md b/site/content/resources/upgrading.md index c805d950bda..eaa9a7d6a58 100644 --- a/site/content/resources/upgrading.md +++ b/site/content/resources/upgrading.md @@ -9,9 +9,74 @@ This document describes the changes needed to upgrade your Contour installation. -## Upgrading Contour 1.19.0 to 1.19.1 +## Upgrading Contour 1.19.1 to 1.20.0 + +Contour 1.20.0 is the current stable release. + +### Required Envoy version + +All users should ensure the Envoy image version is `docker.io/envoyproxy/envoy:v1.21.0`. + +Please see the [Envoy Release Notes][35] for information about issues fixed in Envoy 1.21.0. + +### The easy way to upgrade + +If the following are true for you: + +* Your installation is in the `projectcontour` namespace. +* You are using our [quickstart example][18] deployments. +* Your cluster can take a few minutes of downtime. + +Then the simplest way to upgrade to 1.20.0 is to delete the `projectcontour` namespace and reapply one of the example configurations: -Contour 1.19.1 is the current stable release. +```bash +$ kubectl delete namespace projectcontour +$ kubectl apply -f {{< param base_url >}}/quickstart/v1.20.0/contour.yaml +``` + +This will remove the Envoy and Contour pods from your cluster and recreate them with the updated configuration. +If you're using a `LoadBalancer` Service, (which most of the examples do) deleting and recreating may change the public IP assigned by your cloud provider. +You'll need to re-check where your DNS names are pointing as well, using [Get your hostname or IP address][12]. + +### The less easy way + +This section contains information for administrators who wish to apply the Contour 1.19.1 to 1.20.0 changes manually. +The YAML files referenced in this section can be found by cloning the Contour repository and checking out the `v1.20.0` tag. + +If your version of Contour is older than v1.19.1, please upgrade to v1.19.1 first, then upgrade to v1.20.0. + +1. Update the Contour CRDs: + + ```bash + $ kubectl apply -f examples/contour/01-crds.yaml + ``` + +1. Users of the example deployment should reapply the certgen Job YAML which will re-generate the relevant Secrets in a format compatible with [cert-manager](https://cert-manager.io) TLS secrets. + This will rotate the TLS certificates used for gRPC security. + + ```bash + $ kubectl apply -f examples/contour/02-job-certgen.yaml + ``` + +1. Update the Contour cluster role: + + ```bash + $ kubectl apply -f examples/contour/02-role-contour.yaml + ``` + +1. Upgrade the Contour deployment: + + ```bash + $ kubectl apply -f examples/contour/03-contour.yaml + ``` + +1. Once the Contour deployment has finished upgrading, update the Envoy DaemonSet: + + ```bash + $ kubectl apply -f examples/contour/03-envoy.yaml + ``` + +## Upgrading Contour 1.19.0 to 1.19.1 ### Required Envoy version @@ -1964,3 +2029,4 @@ $ kubectl get configmap -n heptio-contour -o yaml contour [32]: https://www.envoyproxy.io/docs/envoy/v1.18.3/version_history/current [33]: https://www.envoyproxy.io/docs/envoy/v1.19.0/version_history/current [34]: https://www.envoyproxy.io/docs/envoy/v1.19.1/version_history/current +[35]: https://www.envoyproxy.io/docs/envoy/v1.21.0/version_history/current diff --git a/site/data/docs/toc-mapping.yml b/site/data/docs/toc-mapping.yml index 322470cae63..2efbda43539 100644 --- a/site/data/docs/toc-mapping.yml +++ b/site/data/docs/toc-mapping.yml @@ -42,3 +42,4 @@ v1.18.2: v1-18-2-toc v1.18.3: v1-18-3-toc v1.19.0: v1-19-0-toc v1.19.1: v1-19-1-toc +v1.20.0: v1-20-0-toc diff --git a/site/data/docs/v1-20-0-toc.yml b/site/data/docs/v1-20-0-toc.yml new file mode 100644 index 00000000000..a4016688380 --- /dev/null +++ b/site/data/docs/v1-20-0-toc.yml @@ -0,0 +1,121 @@ +toc: + - title: Introduction + subfolderitems: + - page: Contour Architecture + url: /architecture + - page: Contour Philosophy + link: /resources/philosophy + - title: Configuration + subfolderitems: + - page: HTTPProxy Fundamentals + url: /config/fundamentals + - page: Ingress v1 Support + url: /config/ingress + - page: Virtual Hosts + url: /config/virtual-hosts + - page: Inclusion and Delegation + url: /config/inclusion-delegation + - page: TLS Termination + url: /config/tls-termination + - page: Upstream TLS + url: /config/upstream-tls + - page: Request Routing + url: /config/request-routing + - page: External Service Routing + url: /config/external-service-routing + - page: Request Rewriting + url: /config/request-rewriting + - page: CORS + url: /config/cors + - page: Websockets + url: /config/websockets + - page: Upstream Health Checks + url: /config/health-checks + - page: Client Authorization + url: /config/client-authorization + - page: TLS Delegation + url: /config/tls-delegation + - page: Rate Limiting + url: /config/rate-limiting + - page: Access logging + url: /config/access-logging + - page: Annotations Reference + url: /config/annotations + - page: Cookie Rewriting + url: /config/cookie-rewriting + - page: API Reference + url: /config/api + - title: Deployment + subfolderitems: + - page: Deployment Options + url: /deploy-options + - page: Contour Configuration + url: /configuration + - page: Upgrading Contour + link: /resources/upgrading + - page: Enabling TLS between Envoy and Contour + url: /grpc-tls-howto + - page: Redeploy Envoy + url: /redeploy-envoy + - title: Guides + subfolderitems: + - page: AWS with NLB + link: /guides/deploy-aws-nlb/ + - page: Cert-Manager + link: /guides/cert-manager/ + - page: External Authorization + link: /guides/external-authorization/ + - page: JSON logging + link: /guides/structured-logs/ + - page: Migrating to HTTPProxy + link: /guides/ingressroute-to-httpproxy/ + - page: Prometheus Metrics + link: /guides/prometheus/ + - page: PROXY Protocol Support + link: /guides/proxy-proto/ + - page: Resource Limits + link: /guides/resource-limits/ + - title: Troubleshooting + subfolderitems: + - page: Envoy Administration Access + url: /troubleshooting/envoy-admin-interface + - page: Contour Debug Logging + url: /troubleshooting/contour-debug-log + - page: Envoy Debug Logging + url: /troubleshooting/envoy-debug-log + - page: Visualize the Contour Graph + url: /troubleshooting/contour-graph + - page: Show Contour xDS Resources + url: /troubleshooting/contour-xds-resources + - page: Profiling Contour + url: /troubleshooting/profiling-contour + - page: Contour Operator + url: /troubleshooting/operator + - title: Resources + subfolderitems: + - page: Support Policy + link: /resources/support + - page: Compatibility Matrix + link: /resources/compatibility-matrix + - page: Contour Deprecation Policy + link: /resources/deprecation-policy + - page: Release Process + link: /resources/release-process + - page: Frequently Asked Questions + link: /resources/faq + - page: Tagging + link: /resources/tagging + - title: Security + subfolderitems: + - page: Threat Model and Security Posture + link: /resources/security-threat-model + - page: Security Report Process + link: /resources/security-process + - page: Security Fix Checklist + link: /resources/security-checklist + - title: Contribute + subfolderitems: + - page: Start Contributing + url: /start-contributing + - page: How We Work + link: /resources/how-we-work diff --git a/versions.yaml b/versions.yaml index 471bb11bcb8..737c358c6f9 100644 --- a/versions.yaml +++ b/versions.yaml @@ -15,8 +15,19 @@ versions: gateway-api: - v1alpha2 contour-operator: main - - version: v1.19.1 + - version: v1.20.0 supported: "true" + dependencies: + envoy: "1.21.0" + kubernetes: + - "1.23" + - "1.22" + - "1.21" + gateway-api: + - v1alpha2 + contour-operator: v1.20.0 + - version: v1.19.1 + supported: "false" dependencies: envoy: "1.19.1" kubernetes: