Project: Contour (Orchestration & Management / Service Proxy)
File: gatekeeper.md

title: Using Gatekeeper as a validating admission controller with Contour This tutorial demonstrates how to use as a validating admission controller for Contour. Gatekeeper is a project that enables users to define flexible policies for Kubernetes resources using that are enforced when those resources are created/updated via the Kubernetes API. The benefits of using Gatekeeper with Contour are: Immediate feedback for the user when they try to create an `HTTPProxy` with an invalid spec. Instead of having to check the `HTTPProxy`'s status after creation for a possible error message, the create is rejected and the user is immediately provided with a reason for the rejection. User-defined policies for `HTTPProxy` specs. For example, the Contour admin can define policies to enforce maximum limits on timeouts and retries, disallow certain FQDNs, etc. A Kubernetes cluster with a minimum version of 1.14 (to enable webhook timeouts for Gatekeeper). Cluster-admin permissions Run: ```bash $ kubectl apply -f {{< param base_url >}}/quickstart/contour.yaml ``` This creates a `projectcontour` namespace and sets up Contour as a deployment and Envoy as a daemonset, with communication between them secured by mutual TLS. Check the status of the Contour pods with this command: ```bash $ kubectl -n projectcontour get pods -l app=contour NAME READY STATUS RESTARTS AGE contour-8596d6dbd7-9nrg2 1/1 Running 0 32m contour-8596d6dbd7-mmtc8 1/1 Running 0 32m ``` If installation was successful, all pods should reach `Running` status shortly. The following instructions are summarized from the . If you already have Gatekeeper running in your cluster, you can skip this section. Run: ```bash $ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml ``` This creates a `gatekeeper-system` namespace and sets up the Gatekeeper controller manager and audit deployments using the latest Gatekeeper release. Check the status of the Gatekeeper pods with this command: ```bash $ kubectl -n gatekeeper-system get pods NAME READY STATUS RESTARTS AGE gatekeeper-audit-67dfc46db6-kjcmc 1/1 Running 0 40m gatekeeper-controller-manager-7cbc758844-64hhn 1/1 Running 0 40m gatekeeper-controller-manager-7cbc758844-c4dkd 1/1 Running 0 40m gatekeeper-controller-manager-7cbc758844-xv9jn 1/1 Running 0 40m ``` If installation was successful, all pods should reach `Running` status shortly. Gatekeeper uses the to define and enforce policies. This framework has two key types: `ConstraintTemplate` and `Constraint`. A `ConstraintTemplate` defines a reusable OPA policy, along with the parameters that can be passed to it when it is instantiated. When a `ConstraintTemplate` is created, Gatekeeper automatically creates a custom resource definition (CRD) to represent it in the cluster. A `Constraint` is an instantiation of a `ConstraintTemplate`, which tells Gatekeeper to apply it to specific Kubernetes resource types (e.g. `HTTPProxy`) and provides any relevant parameter values. A `Constraint` is defined as an instance of the CRD representing the associated `ConstraintTemplate`. We'll now look at some examples to make these concepts concrete. First, Gatekeeper needs to be configured to store all `HTTPProxy` resources in its internal cache, so that existing `HTTPProxy` resources can be referenced within constraint template

This is essential for being able to define constraints that look across all `HTTPProxies` -- for example, to verify FQDN uniqueness. Create a file called `config.yml` containing the following YAML: ```yaml apiVersion: config.gatekeeper.sh/v1alpha1 kind: Config metadata: name: config namespace: "gatekeeper-system" spec: sync: syncOnly: group: "projectcontour.io" version: "v1" kind: "HTTPProxy" ``` Apply it to the cluster: ```bash $ kubectl apply -f config.yml ``` Note that if you already had Gatekeeper running in your cluster, you may already have the `Config` resource defined. In that case, you'll need to edit the existing resource to add `HTTPProxy` to the `spec.sync.syncOnly` list. The first constraint template and constraint that we'll define are what we'll refer to as a validation. These are rules for `HTTPProxy` specs that Contour universally requires to be true. In this example, we'll define a constraint template and constraint to enforce that all `HTTPProxies` must have a unique FQDN. Create a file called `unique-fqdn-template.yml` containing the following YAML: ```yaml apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: httpproxyuniquefqdn spec: crd: spec: names: kind: HTTPProxyUniqueFQDN listKind: HTTPProxyUniqueFQDNList plural: HTTPProxyUniqueFQDNs singular: HTTPProxyUniqueFQDN targets: target: admission.k8s.gatekeeper.sh rego: | package httpproxy.uniquefqdn violation[{"msg": msg, "other": sprintf("%v/%v", [other.metadata.namespace, other.metadata.name])}] { got := input.review.object.spec.virtualhost.fqdn other := data.inventory.namespace other.spec.virtualhost.fqdn = got not same(other, input.review.object) msg := "HTTPProxy must have a unique spec.virtualhost.fqdn" } same(a, b) { a.metadata.namespace == b.metadata.namespace a.metadata.name == b.metadata.name } ``` Apply it to the cluster: ```bash $ kubectl apply -f unique-fqdn-template.yml ``` Within a few seconds, you'll see that a corresponding CRD has been created in the cluster: ```bash $ kubectl get crd httpproxyuniquefqdn.constraints.gatekeeper.sh NAME CREATED AT httpproxyuniquefqdn.constraints.gatekeeper.sh 2020-08-13T16:08:57Z ``` Now, create a file called `unique-fqdn-constraint.yml` containing the following YAML: ```yaml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: HTTPProxyUniqueFQDN metadata: name: httpproxy-unique-fqdn spec: match: kinds: apiGroups: ["projectcontour.io"] kinds: ["HTTPProxy"] ``` Note that the `Kind` of this resource corresponds to the new CRD. Apply it to the cluster: ```bash $ kubectl apply -f unique-fqdn-constraint.yml ``` Now, let's create some `HTTPProxies` to see the validation in action. Create a file called `httpproxies.yml` containing the following YAML: ```yaml apiVersion: projectcontour.io/v1 kind: HTTPProxy metadata: name: demo namespace: default spec: virtualhost: fqdn: demo.projectcontour.io apiVersion: projectcontour.io/v1 kind: HTTPProxy metadata: name: demo2 namespace: default spec: virtualhost: fqdn: demo.projectcontour.io ``` Note that both `HTTPProxies` have the same FQDN. Apply the YAML: ```bash $ kubectl apply -f httpproxies.yml ``` You should see something like: ``` httpproxy.projectcontour.io/demo created Error from server ([denied by httpproxy-unique-fqdn] HTTPProxy must have a unique FQDN): error when creating "httpproxies.yml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by httpproxy-unique-fqdn] HTTPProxy must have a unique FQDN ``` The first `HTTPProxy` was created successfully, because there was not already an existing proxy with the `demo.projectcontour.io` FQDN. However, when the second `HTTPProxy` was submitted, Gatekeeper rejected its creation because it used the same FQDN as the first

The next constraint template and constraint that we'll create are what we refer to as a policy. These are rules for `HTTPProxy` specs that an individual Contour administrator may want to enforce for their cluster, but that are not explicitly required by Contour itself. In this example, we'll define a constraint template and constraint to enforce that all `HTTPProxies` can be configured with at most five retries for any route. Create a file called `retry-count-range-template.yml` containing the following YAML: ```yaml apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: httpproxyretrycountrange spec: crd: spec: names: kind: HTTPProxyRetryCountRange listKind: HTTPProxyRetryCountRangeList plural: HTTPProxyRetryCountRanges singular: HTTPProxyRetryCountRange scope: Namespaced validation: openAPIV3Schema: properties: min: type: integer max: type: integer targets: target: admission.k8s.gatekeeper.sh rego: | package httpproxy.retrycountrange retry_counts[val] { val := input.review.object.spec.routes[_].retryPolicy.count } violation[{"msg": msg}] { retrycounts[] > input.parameters.max msg := sprintf("retry count must be less than or equal to %v", [input.parameters.max]) } violation[{"msg": msg}] { retrycounts[] < input.parameters.min msg := sprintf("retry count must be greater than or equal to %v", [input.parameters.min]) } ``` Apply it to the cluster: ```bash $ kubectl apply -f retry-count-range-template.yml ``` Again, within a few seconds, you'll see that a corresponding CRD has been created in the cluster: ```bash $ kubectl get crd httpproxyretrycountrange.constraints.gatekeeper.sh NAME CREATED AT httpproxyretrycountrange.constraints.gatekeeper.sh 2020-08-13T16:12:10Z ``` Now, create a file called `retry-count-range-constraint.yml` containing the following YAML: ```yaml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: HTTPProxyRetryCountRange metadata: name: httpproxy-retry-count-range spec: match: kinds: apiGroups: ["projectcontour.io"] kinds: ["HTTPProxy"] namespaces: my-namespace parameters: max: 5 ``` Note that for this `Constraint`, we've added a `spec.match.namespaces` field which defines that this policy should only be applied to `HTTPProxies` created in the `my-namespace` namespace. If this `namespaces` matcher is not specified, then the `Constraint` applies to all namespaces. You can read more about `Constraint` matchers on the . Apply it to the cluster: ```bash $ kubectl apply -f retry-count-range-constraint.yml ``` Now, let's create some `HTTPProxies` to see the policy in action. Create a namespace called `my-namespace`: ```bash $ kubectl create namespace my-namespace namespace/my-namespace created ``` Create a file called `httpproxy-retries.yml` containing the following YAML: ```yaml apiVersion: projectcontour.io/v1 kind: HTTPProxy metadata: name: demo-retries namespace: my-namespace spec: virtualhost: fqdn: retries.projectcontour.io routes: conditions: prefix: /foo services: name: s1 port: 80 retryPolicy: count: 6 ``` Apply the YAML: ```bash $ kubectl apply -f httpproxy-retries.yml ``` You should see something like: ``` Error from server ([denied by httpproxy-retry-count-range] retry count must be less than or equal to 5): error when creating "proxy-retries.yml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by httpproxy-retry-count-range] retry count must be less than or equal to 5 ``` Now, change the `count` field on the last line of `httpproxy-retries.yml` to have a value of `5`. Save the file, and apply it again: ```bash $ kubectl apply -f httpproxy-retries.yml ``` Now the `HTTPProxy` creates successfully*. Note that the HTTPProxy is still marked invalid by Contour after creation because the service `s1` does not exist, but that's outside the scope of this guide. Finally, create a file called

containing the following YAML: ```yaml apiVersion: projectcontour.io/v1 kind: HTTPProxy metadata: name: demo-retries namespace: default spec: virtualhost: fqdn: default.retries.projectcontour.io routes: conditions: prefix: /foo services: name: s1 port: 80 retryPolicy: count: 6 ``` Remember that our `Constraint` was defined to apply only to the `my-namespace` namespace, so it should not block the creation of this proxy, even though it has a retry policy count outside the allowed range. Apply the YAML: ```bash $ kubectl apply -f httpproxy-retries-default.yml ``` The `HTTPProxy` creates successfully. We've seen how Gatekeeper constraints can enforce constraints when a user tries to create a new `HTTPProxy`. Now let's look at how constraints can be applied to pre-existing resources in the cluster. Gatekeeper has an audit functionality, that periodically (every `60s` by default) checks all existing resources against the relevant set of constraints. Any violations are reported in the `Constraint` custom resource's `status.violations` field. This allows an administrator to periodically review & correct any pre-existing misconfigurations, while not having to worry about breaking existing resources when rolling out a new or updated constraint. To try this out, let's revisit the previous example, and change our constraint to allow a maximum retry count of four. Edit `retry-count-range-constraint.yml` and change the `max` field to have a value of `4`. Save the file. Apply it to the cluster: ```bash $ kubectl apply -f retry-count-range-constraint.yml ``` We know that the `demo-retries` proxy has a route with a `retryPolicy.count` of `5`. This should now be invalid according to the updated constraint. Wait up to `60s` for the next periodic audit to finish, then run: ```bash $ kubectl describe httpproxyretrycountrange httpproxy-retry-count-range ``` You should see something like: ``` ... Status: ... Violations: Enforcement Action: deny Kind: HTTPProxy Message: retry policy count must be less than or equal to 4 Name: demo-retries Namespace: my-namespace ``` However, our `HTTPProxy` remains in the cluster and can continue to route requests, and the user can remediate the proxy to bring it inline with the policy on their own timeline. Contour has a of Gatekeeper constraint templates and constraints, for both validations and policies. If you're using Gatekeeper, we recommend that you apply all of the validations we've defined, since these rules are already being checked internally by Contour and reported as status errors/invalid proxies. Using the Gatekeeper constraints will only improve the user experience since users will get earlier feedback if their proxies are invalid. The validations can be found in `examples/gatekeeper/validations`. You should take more of a pick-and-choose approach to our sample policies, since every organization will have different policy needs. Feel free to use any/all/none of them, and augment them with your own policies if applicable. The sample policies can be found in `examples/gatekeeper/policies`. And of course, if you do develop any new constraints that you think may be useful for the broader Contour community, we welcome contributions!