Skip to content

Commit

Permalink
docs: Remove unecessary additions and restructure to one policy
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Fiddes <[email protected]>
  • Loading branch information
hawksight committed Jan 12, 2024
1 parent 1cb7585 commit c5cc28e
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 142 deletions.
160 changes: 66 additions & 94 deletions content/docs/tutorials/certificate-defaults/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,30 @@ By setting custom defaults across our cluster, we enable platform teams to tackl

Use a `ClusterPolicy` to set custom default values for the `Certificate.Spec.PrivateKey` fields.

- **To to default the `Issuer` for users within the cluster.**
- **To default the `Issuer` for users within the cluster.**

Use a `ClusterPolicy` to set a custom default for the `Certificate.spec.issuerRef` fields.

- **To set a default pattern for the naming of the `Secret` where the certificate will be populated.**

Use a `ClusterPolicy` to set a custom default value for the `spec.secretName` required field.

- **To enable users to request a certificate by providing only one certificate identity field**

Use a `ClusterPolicy` to set all other required `Certificate.spec` fields.
A `Certificate` resource user will then only need to supply a single specification field, one of:
- `commonName` or `literalSubject`
- `dnsNames`
- `uris`
- `emailAddresses`
- `ipAddresses`
- `otherNames`

## Process

We will set up defaults for three different scenarios, getting slightly more advanced each time:

1. Setting defaults for optional `Certificate` resource fields.
1. Setting defaults for optional `Certificate` resource fields.
2. Setting defaults for required `Certificate` resource fields.
3. Setting defaults for `Certificate` resource fields, when using `Ingress` annotations to request certificates.

Expand All @@ -50,8 +65,6 @@ We will set up defaults for three different scenarios, getting slightly more adv
1. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): The Kubernetes
command-line tool which allows you to configure Kubernetes clusters.
1. [helm](https://helm.sh/): A package manager for Kubernetes.
1. [yq](https://github.com/mikefarah/yq#install): A command-line tool for
parsing YAML with helpful coloring.
1. [kind](https://kind.sigs.k8s.io/) (**OPTIONAL**): For creating a local
Kubernetes environment that runs in Docker or other container runtimes.

Expand All @@ -65,7 +78,7 @@ We will set up defaults for three different scenarios, getting slightly more adv
kind create cluster --name defaults
```

> ⏲ It should take less than 1 minute to create the cluster, depending on your machine.
> ⏲ It should take less than one minute to create the cluster, depending on your machine.
>
> ⚠️ This cluster is only suitable for learning purposes. It is not suitable for production use.

Expand Down Expand Up @@ -118,7 +131,6 @@ Once you have your cluster environment, install the required Kubernetes packages
> - [Kyverno installation instructions](https://kyverno.io/docs/installation/methods/#install-kyverno-using-helm)
> - [ingress-nginx installation instructions](https://kubernetes.github.io/ingress-nginx/deploy/)


# Setting Defaults

The main tutorial starts here with some background, before tackling each of the three scenarios.
Expand All @@ -141,34 +153,34 @@ That means anyone can follow this tutorial even without their own domain.
## 1 - Defaulting optional fields
In this section we will create rules which set 3 fields for all `Certificate` resources automatically.
None of the 3 fields here are required fields, but they might need to be set depending on platform and issuer preferences.
In this section we will create rules which set three fields for all `Certificate` resources automatically.
None of the three fields here are required fields, but they might need to be set depending on platform and issuer preferences.
These rules will:
- Set a default value of: `revisionHistoryLimit: 2`.
- Set a default value of `Always` under `spec.privateKey.rotationPolicy`.
- Set a [default value of `Always` under `spec.privateKey.rotationPolicy`](../../usage/certificate/#the-rotationpolicy-setting).
- Set defaults for all `spec.privateKey` fields.
> ℹ️ Note how these rules tackle the first two of our [3 uses cases](#use-cases).
> ℹ️ Note how these rules tackle the first two of our [uses cases](#use-cases).
1. First take a look at the `ClusterPolicy`:
```yaml file=../../../../public/docs/tutorials/certificate-defaults/cpol-mutate-certificate-defaults.yaml
```yaml file=../../../../public/docs/tutorials/certificate-defaults/cpol-mutate-certificates-0.yaml
```
🔗 <a href="cpol-mutate-certificate-defaults.yaml">`cpol-mutate-certificate-defaults.yaml`</a>
🔗 <a href="cpol-mutate-certificates-0.yaml">`cpol-mutate-certificates-0.yaml`</a>
1. Apply the policy to the cluster and check that it is ready:
```shell
kubectl apply -f cpol-mutate-certificate-defaults.yaml
kubectl apply -f cpol-mutate-certificates-0.yaml
kubectl get cpol
```
When the `ClusterPolicy` is ready the output should look like this:
```log
NAME BACKGROUND VALIDATE ACTION READY AGE MESSAGE
0-mutate-certificate-defaults true Audit True 4s Ready
mutate-certificates true Audit True 4s Ready
```
1. Now inspect the "test-revision" `Certificate`:
Expand Down Expand Up @@ -258,27 +270,29 @@ These rules will:
## 2 - Defaulting required fields
> ⚠️ This section requires cert-manager v1.14.x or newer to work properly out of the box.
> If using an older version of cert-manager, see the [Appendix](#cert-manager-version-requirement) section for a full explanation.
> See the [Appendix](#cert-manager-version-requirement) section for details.
Now we can set a Kyverno `ClusterPolicy` to apply default values to any of the `Certificate` fields.
This includes the *required* fields.
In our example `ClusterPolicy` we will do two things:
- Apply a default `secretName` that is the name of the Certificate object suffixed with "-cert".
- Set the relevant `issuerRef` fields to default to use the "our-corp-issuer" `ClusterIssuer`.
- Apply a default `secretName` that is the name of the `Certificate` object suffixed with "-cert".
> ℹ️ Note how the second rule is tackling the third of our [three uses cases](#use-cases).
> ℹ️ Note how these rules are tackling the third and fourth [uses cases](#use-cases).
1. Here is the `ClusterPolicy` resource to set both fields with defaults:
```yaml file=../../../../public/docs/tutorials/certificate-defaults/cpol-mutate-certificate-required.yaml
```yaml file=../../../../public/docs/tutorials/certificate-defaults/cpol-mutate-certificates-1.yaml
```
🔗 <a href="cpol-mutate-certificate-required.yaml">`cpol-mutate-certificate-required.yaml`</a>
🔗 <a href="cpol-mutate-certificates-1.yaml">`cpol-mutate-certificates-1.yaml`</a>
This `ClusterPolicy` is an extension of the policy we applied previously.
1. Apply this policy:
```shell
kubectl apply -f cpol-mutate-certificate-required.yaml
kubectl apply -f cpol-mutate-certificates-1.yaml
```
You should now see two `ClusterPolicy` resources if you run:
Expand All @@ -291,21 +305,10 @@ In our example `ClusterPolicy` we will do two things:
```shell
NAME ADMISSION BACKGROUND VALIDATE ACTION READY AGE MESSAGE
0-mutate-certificate-defaults true true Audit True 3m6s Ready
1-mutate-certificate-required true true Audit True 4s Ready
```
1. Patch the previous policy we applied to disable schema validation after mutation:
```shell
kubectl patch cpol 0-mutate-certificate-defaults --type=merge -p '{"spec":{"schemaValidation": false}}'
mutate-certificates true true Audit True 3m6s Ready
```
> ⚠️ Read about the schema validation setting before using this elsewhere.
> We have disabled schema validation here because we have two policies both affecting `Certificates`. Cert-manager will validate the resolved resource with the `ValidatingWebhookConfiguration` resource after all mutations are applied.
> You could avoid this completely by using one policy with all required mutation rules.
1. Look at the "test-minimal" `Certificate` designed to validate that all our policies are operative:
1. Look at the "test-minimal" `Certificate` designed to validate that all our rules within the policy are operative:
```yaml file=../../../../public/docs/tutorials/certificate-defaults/cert-test-minimal.yaml
```
Expand Down Expand Up @@ -350,9 +353,10 @@ In our example `ClusterPolicy` we will do two things:
+ secretName: test-minimal-cert
```
See how both Kyverno `ClusterPolicies` have been applied and resulted in all the other default values being inserted for us!
See how we have automatically populated the `spec.issuerRef` and `spec.secretName` field values.
This indicates the Kyverno `ClusterPolicy` has been applied to the supplied `Certificate` resource.
1. To be absolutely sure we have not enforced any settings, let us explicitly set each property for which we have a default rule in a `Certificate`. We will use the "test-revision-override" `Certificate`:
1. To be absolutely sure we have not enforced any settings, let us explicitly set each property of the `Certificate` for which we have a default rule. We will use the "test-revision-override" `Certificate`:
```yaml file=../../../../public/docs/tutorials/certificate-defaults/cert-test-revision-override.yaml
```
Expand Down Expand Up @@ -396,13 +400,13 @@ Many cert-manager users don't create `Certificate` resources directly and instea
cert-manager creates `Certificate` resources based on the [supported annotations](https://cert-manager.io/docs/usage/ingress/#supported-annotations) and the `Ingress` specification.
Let's see how we can still use `ClusterPolicy` to apply our defaults in this use case.
1. Check the example `Ingress` resource has the correct annotation to select an `Issuer` or `ClusterIssuer`:
1. This example `Ingress` resource has a `cert-manager.io/cluster-issuer` annotation which instructs cert-manager to create a `Certificate` with an `issuerRef` field pointing at a `ClusterIssuer` called `our-corp-issuer`:
```yaml file=../../../../public/docs/tutorials/certificate-defaults/ingress.yaml
```
🔗 <a href="ingress.yaml">`ingress.yaml`</a>
1. This annotation and the relevant `ingress.spec.tls` configuration are all we need:
1. This annotation and the relevant `ingress.spec.tls` configuration are all we need so apply the resource:
```shell
kubectl apply -f ingress.yaml
Expand All @@ -411,7 +415,7 @@ Let's see how we can still use `ClusterPolicy` to apply our defaults in this use
1. Now validate that the `Certificate` resource was automatically generated:
```shell
kubectl get cert defaults-example-certificate-tls -o yaml | yq
kubectl get cert defaults-example-certificate-tls -o yaml
```
This command should return some output similar to this example:
Expand Down Expand Up @@ -464,9 +468,10 @@ Let's see how we can still use `ClusterPolicy` to apply our defaults in this use
reason: DoesNotExist
status: "False"
type: Ready
nextPrivateKeySecretName: defaults-example-certificate-tls-2hmpj ```
nextPrivateKeySecretName: defaults-example-certificate-tls-2hmpj
```
1. You can optionally validate that only the "0-mutate-certificate-defaults" `ClusterPolicy` has been applied by viewing the logs of the Kyverno admission controller container.
1. You can optionally validate that the "mutate-certificates" `ClusterPolicy` has been applied by viewing the logs of the Kyverno admission controller container.
```shell
kubectl logs -n kyverno-system $(kubectl get pod -n kyverno-system -l app.kubernetes.io/component=admission-controller -o jsonpath='{.items[0].metadata.name}') -c kyverno --tail 5
Expand All @@ -491,20 +496,27 @@ Let's see how we can still use `ClusterPolicy` to apply our defaults in this use
See the `object` key indicates that our policy has been applied.
In the `note` section you can identify that it was applied to the `Certificate` resource that was created by cert-manager based on the `Ingress` resource that we applied.
Whilst you are not able to default the `secretName` and `issuerRef` fields when using the ingress-shim, you can default all other fields.
This is reasonable given that the `Ingress` specification needs to know what `Secret` to mount to the ingress controller.
When using an `Ingress` resource, you always need to specify the `secretName` from which to load the certificate.
No defaulting is required in this use case because this is a required part of the `Ingress` specification.
The only additional YAML that a user is required to specify on the `Ingress` resource is the annotation:
```yaml
cert-manager.io/cluster-issuer: "our-corp-issuer"
```
This annotation serves as both the trigger for cert-manager to act upon this `Ingress` and also as the configuration value for the `Certificate.spec.issuerRef` fields.
This single line replaces the need for the user to create a `Certificate` resource entirely.
This results in a reduction of the total YAML required to secure the application behind this `Ingress`.
> **Note**: The `ClusterIssuer` can be defaulted by supplying an install time parameter to cert-manager.
> See [cert-manager documentation](../../usage/ingress.md#optional-configuration) for full details.
> You would still need to provide at least one annotation, however this time it is static and doesn't need to be defaulted.
# Summary
This is a fairly simple example of how easy it can be to setup *defaults* for your cluster `Certificate` resources.
We've shown how a `ClusterPolicy` doesn't have to "enforce" settings, rather it can be used to set and extend the default options.
`Certificate` users can reduce their YAML, whilst maintaining the flexibility to override any value when needed.
We have shown how a few simple policies can change the user experience creating `Certificate` resources from:
We have shown how a simple `ClusterPolicy` with only 5 rules can change the user experience creating `Certificate` resources from:
```yaml file=../../../../public/docs/tutorials/certificate-defaults/cert-test-revision-override.yaml
```
Expand All @@ -516,12 +528,10 @@ To instead only need to specify the configuration important to them, for example
```
🔗 <a href="cert-test-minimal.yaml">`cert-test-minimal.yaml`</a>
With these policies we achieved our objective and have enabled users to submit minimal `Certifiate` resources, with only a single field contained within the specification, the `dnsNames` entry.
With this policy we achieved our objective and have enabled users to submit minimal `Certifiate` resources.
This completes our fifth [use case](#use-cases), with only a single field contained within the specification, the `dnsNames` entry.
Every other specified field was automatically defaulted using Kyverno with `ClusterPolicy` which would typically be setup by a platform administrator.
You may have noticed in the second Kyverno `ClusterPolicy` that we used some `Certificate` resource metadata to create the `secretName` field.
You can read more about this in the [Kyverno documentation](https://kyverno.io/docs/writing-policies/variables/#variables-from-admission-review-requests).

# Cleanup
If you created the kind cluster for this tutorial you can simply run:
Expand All @@ -534,7 +544,8 @@ Otherwise to remove all resources deployed in this tutorial:
```shell
# Assuming you are running from this directly or saved all the files to yamls/
kubectl delete -f yamls/
kubectl delete -f ingress.yaml
kubectl delete -f cpol-mutate-certificates-1.yaml
helm uninstall kyverno -n kyverno-system
helm uninstall cert-manager -n cert-manager
helm uninstall ingress-nginx -n ingress-nginx
Expand All @@ -544,53 +555,14 @@ helm uninstall ingress-nginx -n ingress-nginx
## cert-manager version requirement
Prior to cert-manager version v1.14.x, cert-manager's `MutatingWebhookConfiguration` was triggered by all cert-manager.io resources including `Certificates`.
In reality this webhook is only in place to affect `CertificateRequest` resources, but had an unintended consequence that meant Kyverno policies as we had written would not operate as intended.
When a `Certificate` resource is applied to a Kubernetes cluster, mutating webhooks are applied before validating webhooks.
When the existing cert-manager `MutatingWebhookConfiguration` runs it will add the required field with an empty value, such as: `secretName: ""`, if there is no value in a required field.
The consequence of this action is that our Kyverno policies will not apply as an empty value is already present.
Starting with v1.14.x the cert-manager-webhook `MutatingWebhookConfiguration` resource has been scoped to only affect `CertificateRequest` resources.
It no longer triggers for `Certificate` resources, which our policies in this tutorial are acting on.
If you are running a cert-manager installation prior to v1.14.x you should first consider upgrading.
If upgrading is not feasible right now then you will need to consider one of the following potential fixes for this issue:
1. Rename the 'cert-manager-webhook' mutating and validating webhooks with `z-<existing name>` so that they execute last, after the Kyverno webhooks.
1. Fix the cert-manager mutating webhook not trigger for `Certificate` resource changes, as in [PR #6311](https://github.com/cert-manager/cert-manager/pull/6311).
1. Use enforcement in your policy to explicitly override the value regardless of what the user sets.
Option 1 seems to work but generally should be avoided as order cannot be counted on. Mutating webhooks should also be [idempotent](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#best-practices-and-warnings).
Option 3 defeats the point of allowing a user to override when needed, so discounted for this tutorial.
Option 2 is recommended, as this is the fix that is implemented in later versions of cert-manager, based on [PR #6311](https://github.com/cert-manager/cert-manager/pull/6311).
To manually patch the webhook we have a YAML patch resource:
```yaml file=../../../../public/docs/tutorials/certificate-defaults/mutatingwebhookconfiguration-patch.yaml
```
🔗 <a href="mutatingwebhookconfiguration-patch.yaml">`mutatingwebhookconfiguration-patch.yaml`</a>
To validate exactly what this patch will do, simply run the following to see a `kubectl diff`:
```shell
kubectl patch mutatingwebhookconfigurations.admissionregistration.k8s.io cert-manager-webhook --patch-file patches/mutatingwebhookconfiguration-patch.yaml --dry-run=server -o yaml | kubectl diff -f -
```
And to apply the patch so it takes effect:
```shell
kubectl patch mutatingwebhookconfigurations.admissionregistration.k8s.io cert-manager-webhook --patch-file patches/mutatingwebhookconfiguration-patch.yaml
```
At this point you should now be able to continue with the rest of this tutorial.
The behaviour of cert-manager's mutating webhook has been changed from v1.14.x onward.
For a more complete explanation and details of the change please refer to [PR #6311](https://github.com/cert-manager/cert-manager/pull/6311).
Instructions for a manual fix can be found [in this comment on PR #6311](https://github.com/cert-manager/cert-manager/pull/6311#issuecomment-1889517418).

## Presets Feature Request

For further background reading around setting "defaults" or "presets", you can refer to [issue 2239](ttps://github.com/cert-manager/cert-manager/issues/2239).
This tutorial came out of an investigation of that issue.

The cert-manager team reasoned that requested solution could be achieved with the use of other, more generic open-source policy tools.
The cert-manager team reasoned that the requested solution could be achieved with the use of other, more generic open-source policy tools.
Kyverno is just one example and similar can be achieved with [Gatekeeper](https://github.com/open-policy-agent/gatekeeper) as an alternative tool.
Loading

0 comments on commit c5cc28e

Please sign in to comment.