Learn how to set Certificate defaults automatically
Last Verified: 19 January 2024
Objective
We will set up a cluster where a user specifies as little YAML as possible in Certificate
resources.
This will be achieved by utilizing Kyverno to apply custom "default" values to the Certificate
fields, that are not specified by a user.
There are some benefits to having defaults:
Certificate
consumers minimize their YAML resources.Certificate
consumers retain flexibility to override fields when needed.- Cluster operators can decide what the default should be, rather than having to rely on built-in defaults from cert-manager.
Use cases
By setting custom defaults across our cluster, we enable platform teams to tackle use cases such as:
-
To ensure that
CertificateRequest
resources get cleaned up.Use a
ClusterPolicy
to set a custom default value for theCertificate.Spec.RevisionHistoryLimit
field. -
To help your users choose secure default key settings for their
Certificate
resources.Use a
ClusterPolicy
to set custom default values for theCertificate.Spec.PrivateKey
fields. -
To default the
Issuer
for users within the cluster.Use a
ClusterPolicy
to set a custom default for theCertificate.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 thespec.secretName
required field. -
Make application developers' lives easier by allowing them to create secure X.509 TLS certificates with the minimum of configuration.
Use a
ClusterPolicy
to set all other requiredCertificate.spec
fields. Only a single identity specification field will be required, one of:commonName
orliteralSubject
dnsNames
uris
emailAddresses
ipAddresses
otherNames
Process
We will set up defaults for three different scenarios, getting slightly more advanced each time:
- Setting defaults for optional
Certificate
resource fields. - Setting defaults for required
Certificate
resource fields. - Setting defaults for
Certificate
resource fields, when usingIngress
annotations to request certificates.
Setup
Prerequisites
💻 Software
- kubectl: The Kubernetes command-line tool which allows you to configure Kubernetes clusters.
- helm: A package manager for Kubernetes.
- kind (OPTIONAL): For creating a local Kubernetes environment that runs in Docker or other container runtimes.
Local Kubernetes Environment
⚠️ This step can be skipped if you have another Kubernetes environment.
-
Create a cluster environment using
kind
for this tutorial.kind create cluster --name defaults⏲ 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.
Software Installation
Once you have your cluster environment, install the required Kubernetes packages using helm
.
-
Set some environment variables for the helm chart versions:
export CERT_MANAGER_CHART_VERSION="v1.14.5" \KYVERNO_CHART_VERSION="3.1.4" \INGRESS_NGINX_CHART_VERSION="4.9.0" -
Install cert-manager
helm upgrade --install cert-manager cert-manager \--namespace cert-manager \--version $CERT_MANAGER_CHART_VERSION \--set installCRDs=true \--set startupapicheck.enabled=false \--create-namespace \--repo https://charts.jetstack.io/ -
Install Kyverno
helm upgrade --install kyverno kyverno \--namespace kyverno-system \--version $KYVERNO_CHART_VERSION \--create-namespace \--repo https://kyverno.github.io/kyverno/ -
Install ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx \--namespace ingress-nginx \--version $INGRESS_NGINX_CHART_VERSION \--create-namespace \--repo https://kubernetes.github.io/ingress-nginx
For complete installation instructions, please refer to the following links:
Setting Defaults
The main tutorial starts here with some background, before tackling each of the three scenarios.
Required vs Non-required
The Certificate
resource has a spec
section with a number of "required" fields.
This means these fields must be present when you create a Certificate
resource.
There are also a number of other fields that are not required to be explicitly defined on each Certificate
resource.
This essentially means the value of one of these fields is either not required, or has defaults defined somewhere else.
That somewhere else could be in the cert-manager code base, or indeed by the issuer that creates and returns the X.509 certificate.
Let's explore how we can manipulate these values to be something custom and make the Certificate
user's life easier.
We will set up some ClusterPolicy
resources and Certificate
resources in this tutorial.
We will make reference to a ClusterIssuer
in the Certificate
spec that doesn't exist, but for this tutorial the ClusterIssuer
is not required as we will not actually be requesting certificates.
That means anyone can follow this tutorial even without their own domain.
⚠️ To make it easy to get started we are using cluster scoped
ClusterPolicy
resources. You can scope your defaults to the namespace level through the use ofPolicy
resources in the future, but that will not be covered in this tutorial.
1 - Defaulting optional fields
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
underspec.privateKey.rotationPolicy
. - Set defaults for all
spec.privateKey
fields.
ℹ️ Note how these rules tackle the first two of our uses cases.
-
First take a look at the
ClusterPolicy
:apiVersion: kyverno.io/v1kind: ClusterPolicymetadata:name: mutate-certificatesspec:failurePolicy: Failrules:# Set a sane default for the history field if not already present- name: set-revisionHistoryLimitmatch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:# +(...) This is the clever syntax for if not already set+(revisionHistoryLimit): 2# Set rotation to always if not already set- name: set-privateKey-rotationPolicymatch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:privateKey:+(rotationPolicy): Always# Set private key details for algorithm and size- name: set-privateKey-detailsmatch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:privateKey:+(algorithm): ECDSA+(size): 521+(encoding): PKCS1 -
Apply the policy to the cluster and check that it is ready:
kubectl apply -f cpol-mutate-certificates-0.yamlkubectl get cpolWhen the
ClusterPolicy
is ready the output should look like this:NAME ADMISSION BACKGROUND VALIDATE ACTION READY AGE MESSAGEmutate-certificates true true Audit True 0s Ready -
Now inspect the "test-revision"
Certificate
:apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: test-revisionnamespace: defaultspec:dnsNames:- example.comissuerRef:group: cert-manager.iokind: ClusterIssuername: not-my-corp-issuersecretName: test-revision-certYou can see that we have set the most minimal configuration currently possible, specifying only a DNS name for the certificate, where to save it (
secretName
) and the issuer to use to request the certificate (issuerRef
). -
Use the following command to dry-run apply the certificate and then
diff
it against the original resource, to see how the defaults from ourClusterPolicy
are applied:kubectl apply -f cert-test-revision.yaml --dry-run=server -o yaml | diff -uZ cert-test-revision.yaml -This command should return some output similar to this example:
--- cert-test-revision.yaml 2024-01-08 12:14:59.225074232 +0000+++ - 2024-01-12 17:37:51.076593214 +0000@@ -1,8 +1,14 @@apiVersion: cert-manager.io/v1kind: Certificatemetadata:+ annotations:+ kubectl.kubernetes.io/last-applied-configuration: |+ {"apiVersion":"cert-manager.io/v1","kind":"Certificate","metadata":{"annotations":{},"name":"test-revision","namespace":"default"},"spec":{"dnsNames":["example.com"],"issuerRef":{"group":"cert-manager.io","kind":"ClusterIssuer","name":"not-my-corp-issuer"},"secretName":"test-revision-cert"}}+ creationTimestamp: "2024-01-12T17:37:51Z"+ generation: 1name: test-revisionnamespace: default+ uid: 9f9a4f0a-4aa7-427d-ae4b-c1716fed8246spec:dnsNames:- example.com@@ -10,4 +16,10 @@group: cert-manager.iokind: ClusterIssuername: not-my-corp-issuer+ privateKey:+ algorithm: ECDSA+ encoding: PKCS1+ rotationPolicy: Always+ size: 521+ revisionHistoryLimit: 2secretName: test-revision-certWe have successfully defaulted the
privateKey
andrevisionHistoryLimit
fields! -
Let's override all of these defaulted fields, to validate that we can still set what we want as an end user. To test this, let's use the "test-revision-override"
Certificate
:apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: test-revision-overridenamespace: defaultspec:dnsNames:- example.comissuerRef:group: cert-manager.iokind: ClusterIssuername: not-my-corp-issuerprivateKey:algorithm: RSAencoding: PKCS8rotationPolicy: Neversize: 4096revisionHistoryLimit: 44secretName: test-revision-override-cert🔗
cert-test-revision-override.yaml
As before, dry-run apply and
diff
the output with the input file:kubectl apply -f cert-test-revision-override.yaml --dry-run=server -o yaml | diff -uZ cert-test-revision-override.yaml -Here you can see in the output there are no specification changes for the
Certificate
itself. TheCertificate
already had all the fields defined that ourClusterPolicy
rules would have affected.--- cert-test-revision-override.yaml 2024-01-05 14:45:14.972562067 +0000+++ - 2024-01-12 17:39:57.217028745 +0000@@ -1,8 +1,14 @@apiVersion: cert-manager.io/v1kind: Certificatemetadata:+ annotations:+ kubectl.kubernetes.io/last-applied-configuration: |+ {"apiVersion":"cert-manager.io/v1","kind":"Certificate","metadata":{"annotations":{},"name":"test-revision-override","namespace":"default"},"spec":{"dnsNames":["example.com"],"issuerRef":{"group":"cert-manager.io","kind":"ClusterIssuer","name":"not-my-corp-issuer"},"privateKey":{"algorithm":"RSA","encoding":"PKCS8","rotationPolicy":"Never","size":4096},"revisionHistoryLimit":44,"secretName":"test-revision-override-cert"}}+ creationTimestamp: "2024-01-12T17:39:57Z"+ generation: 1name: test-revision-overridenamespace: default+ uid: 83a6ddbc-6903-479e-802d-e11149985338spec:dnsNames:- example.com
2 - Defaulting required fields
⚠️ This section requires cert-manager v1.14.x or newer to work properly out of the box. See the Appendix 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:
- Set the relevant
issuerRef
fields to default to use the "our-corp-issuer"ClusterIssuer
. - Apply a default
secretName
that is the name of theCertificate
object suffixed with "-cert".
ℹ️ Note how these rules are tackling the third and fourth uses cases.
-
Here is the
ClusterPolicy
resource to set both fields with defaults:apiVersion: kyverno.io/v1kind: ClusterPolicymetadata:name: mutate-certificatesspec:failurePolicy: Failrules:# Set a sane default for the history field if not already present- name: set-revisionHistoryLimitmatch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:# +(...) This is the clever syntax for if not already set+(revisionHistoryLimit): 2# Set rotation to always if not already set- name: set-privateKey-rotationPolicymatch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:privateKey:+(rotationPolicy): Always# Set private key details for algorithm and size- name: set-privateKey-detailsmatch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:privateKey:+(algorithm): ECDSA+(size): 521+(encoding): PKCS1# Set a secretName when one is not provided- name: set-default-secret-namematch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:# You can read more about this syntax in the Kyverno documentation:# https://kyverno.io/docs/writing-policies/variables/#variables-from-admission-review-requests+(secretName): "{{request.object.metadata.name}}-cert"# Set a default for issuerRef fields- name: set-default-issuer-refmatch:any:- resources:kinds:- Certificatemutate:patchStrategicMerge:spec:+(issuerRef):name: our-corp-issuerkind: ClusterIssuergroup: cert-manager.io🔗
cpol-mutate-certificates-1.yaml
This
ClusterPolicy
is an extension of the policy we applied previously. -
Apply this policy:
kubectl apply -f cpol-mutate-certificates-1.yamlYou should see that our existing
ClusterPolicy
has been changed:clusterpolicy.kyverno.io/mutate-certificates configuredGet the
ClusterPolicy
to validate it is "Ready":kubectl get cpolThis command should return some output similar to this example:
NAME ADMISSION BACKGROUND VALIDATE ACTION READY AGE MESSAGEmutate-certificates true true Audit True 6m21s Ready -
Look at the "test-minimal"
Certificate
designed to validate that all our rules within the policy are operative:apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: test-minimalnamespace: defaultspec:dnsNames:- example.com -
Dry-run apply and
diff
to validate all our defaults have applied to this minimalCertificate
:kubectl apply -f cert-test-minimal.yaml --dry-run=server -o yaml | diff -uZ cert-test-minimal.yaml -This command should return some output similar to this example:
--- cert-test-minimal.yaml 2024-01-05 14:45:07.140668401 +0000+++ - 2024-01-12 17:44:08.110290752 +0000@@ -1,8 +1,25 @@apiVersion: cert-manager.io/v1kind: Certificatemetadata:+ annotations:+ kubectl.kubernetes.io/last-applied-configuration: |+ {"apiVersion":"cert-manager.io/v1","kind":"Certificate","metadata":{"annotations":{},"name":"test-minimal","namespace":"default"},"spec":{"dnsNames":["example.com"]}}+ creationTimestamp: "2024-01-12T17:44:08Z"+ generation: 1name: test-minimalnamespace: default+ uid: 792d29c7-8cf3-4f3a-9f12-4fba396e0d6espec:dnsNames:- example.com+ issuerRef:+ group: cert-manager.io+ kind: ClusterIssuer+ name: our-corp-issuer+ privateKey:+ algorithm: ECDSA+ encoding: PKCS1+ rotationPolicy: Always+ size: 521+ revisionHistoryLimit: 2+ secretName: test-minimal-certSee how we have automatically populated the
spec.issuerRef
andspec.secretName
field values. This indicates the KyvernoClusterPolicy
has been applied to the suppliedCertificate
resource. -
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
:apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: test-revision-overridenamespace: defaultspec:dnsNames:- example.comissuerRef:group: cert-manager.iokind: ClusterIssuername: not-my-corp-issuerprivateKey:algorithm: RSAencoding: PKCS8rotationPolicy: Neversize: 4096revisionHistoryLimit: 44secretName: test-revision-override-cert -
Dry-run apply and
diff
this file:kubectl apply -f cert-test-revision-override.yaml --dry-run=server -o yaml | diff -uZ cert-test-revision-override.yaml -This command should return some output similar to this example:
--- cert-test-revision-override.yaml 2024-01-05 14:45:14.972562067 +0000+++ - 2024-01-12 17:45:48.261997150 +0000@@ -1,8 +1,14 @@apiVersion: cert-manager.io/v1kind: Certificatemetadata:+ annotations:+ kubectl.kubernetes.io/last-applied-configuration: |+ {"apiVersion":"cert-manager.io/v1","kind":"Certificate","metadata":{"annotations":{},"name":"test-revision-override","namespace":"default"},"spec":{"dnsNames":["example.com"],"issuerRef":{"group":"cert-manager.io","kind":"ClusterIssuer","name":"not-my-corp-issuer"},"privateKey":{"algorithm":"RSA","encoding":"PKCS8","rotationPolicy":"Never","size":4096},"revisionHistoryLimit":44,"secretName":"test-revision-override-cert"}}+ creationTimestamp: "2024-01-12T17:45:48Z"+ generation: 1name: test-revision-overridenamespace: default+ uid: d0ad7abe-c703-45f7-acf9-634b3a263cfaspec:dnsNames:- example.comFrom this command you can see that none of the
Certificate
specification fields have been changed. Only the metadata section has changed which tells us the policies have applied but not set any defaults because values were already provided. This shows that you retain the flexibility to override the cluster defaults when needed.
3 - Defaulting through Ingress Annotations
Many cert-manager users don't create Certificate
resources directly and instead use the ingress-shim functionality.
cert-manager creates Certificate
resources based on the supported annotations and the Ingress
specification.
Let's see how we can still use ClusterPolicy
to apply our defaults in this use case.
-
This example
Ingress
resource has acert-manager.io/cluster-issuer
annotation which instructs cert-manager to create aCertificate
with anissuerRef
field pointing at aClusterIssuer
calledour-corp-issuer
:apiVersion: networking.k8s.io/v1kind: Ingressmetadata:annotations:cert-manager.io/cluster-issuer: "our-corp-issuer"name: defaults-examplenamespace: defaultspec:ingressClassName: nginxrules:- host: app.example.comhttp:paths:- backend:service:name: appport:number: 80path: /pathType: ImplementationSpecifictls:- hosts:- app.example.comsecretName: defaults-example-certificate-tls -
This annotation and the relevant
ingress.spec.tls
configuration are all we need so apply the resource:kubectl apply -f ingress.yaml -
Now validate that the
Certificate
resource was automatically generated:kubectl get cert defaults-example-certificate-tls -o yamlThis command should return some output similar to this example:
apiVersion: cert-manager.io/v1kind: Certificatemetadata:creationTimestamp: "2024-01-12T17:47:04Z"generation: 1name: defaults-example-certificate-tlsnamespace: defaultownerReferences:- apiVersion: networking.k8s.io/v1blockOwnerDeletion: truecontroller: truekind: Ingressname: defaults-exampleuid: bea33a55-a9ed-4664-a56a-a679eb8272c3resourceVersion: "584260"uid: 43ced989-723b-4eac-bad0-f8bead6976dfspec:dnsNames:- app.example.comissuerRef:group: cert-manager.iokind: ClusterIssuername: our-corp-issuerprivateKey:algorithm: ECDSAencoding: PKCS1rotationPolicy: Alwayssize: 521revisionHistoryLimit: 2secretName: defaults-example-certificate-tlsusages:- digital signature- key enciphermentstatus:conditions:- lastTransitionTime: "2024-01-12T17:47:04Z"message: Issuing certificate as Secret does not existobservedGeneration: 1reason: DoesNotExiststatus: "True"type: Issuing- lastTransitionTime: "2024-01-12T17:47:04Z"message: Issuing certificate as Secret does not existobservedGeneration: 1reason: DoesNotExiststatus: "False"type: ReadynextPrivateKeySecretName: defaults-example-certificate-tls-nbjws -
You can optionally validate that the "mutate-certificates"
ClusterPolicy
has been applied by viewing the logs of the Kyverno admission controller container.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 3This command should return some output similar to this example:
I0112 17:47:04.425863 1 mutation.go:113] webhooks/resource/mutate "msg"="mutation rules from policy applied successfully" "clusterroles"=["cert-manager-controller-approve:cert-manager-io","cert-manager-controller-certificates","cert-manager-controller-certificatesigningrequests","cert-manager-controller-challenges","cert-manager-controller-clusterissuers","cert-manager-controller-ingress-shim","cert-manager-controller-issuers","cert-manager-controller-orders","system:basic-user","system:discovery","system:public-info-viewer","system:service-account-issuer-discovery"] "gvk"={"group":"cert-manager.io","version":"v1","kind":"Certificate"} "gvr"={"group":"cert-manager.io","version":"v1","resource":"certificates"} "kind"="Certificate" "name"="defaults-example-certificate-tls" "namespace"="default" "operation"="UPDATE" "policy"="mutate-certificates" "resource.gvk"={"Group":"cert-manager.io","Version":"v1","Kind":"Certificate"} "roles"=["kube-system:cert-manager:leaderelection"] "rules"=["set-revisionHistoryLimit","set-privateKey-rotationPolicy","set-privateKey-details"] "uid"="6f93bd8d-29ca-4eab-8e96-065ea82a1bf2" "user"={"username":"system:serviceaccount:cert-manager:cert-manager","uid":"21cbad67-9d2e-44ee-bb02-7fef9aa2e502","groups":["system:serviceaccounts","system:serviceaccounts:cert-manager","system:authenticated"],"extra":{"authentication.kubernetes.io/pod-name":["cert-manager-648cd49b44-z6g8s"],"authentication.kubernetes.io/pod-uid":["4bd741fa-a8ec-48a1-82d5-26c5b7acce5e"]}}I0112 17:47:04.458402 1 mutation.go:113] webhooks/resource/mutate "msg"="mutation rules from policy applied successfully" "clusterroles"=["cert-manager-controller-approve:cert-manager-io","cert-manager-controller-certificates","cert-manager-controller-certificatesigningrequests","cert-manager-controller-challenges","cert-manager-controller-clusterissuers","cert-manager-controller-ingress-shim","cert-manager-controller-issuers","cert-manager-controller-orders","system:basic-user","system:discovery","system:public-info-viewer","system:service-account-issuer-discovery"] "gvk"={"group":"cert-manager.io","version":"v1","kind":"Certificate"} "gvr"={"group":"cert-manager.io","version":"v1","resource":"certificates"} "kind"="Certificate" "name"="defaults-example-certificate-tls" "namespace"="default" "operation"="UPDATE" "policy"="mutate-certificates" "resource.gvk"={"Group":"cert-manager.io","Version":"v1","Kind":"Certificate"} "roles"=["kube-system:cert-manager:leaderelection"] "rules"=["set-revisionHistoryLimit","set-privateKey-rotationPolicy","set-privateKey-details"] "uid"="ec61a3c9-df0a-4daf-8bc3-227dc80348a9" "user"={"username":"system:serviceaccount:cert-manager:cert-manager","uid":"21cbad67-9d2e-44ee-bb02-7fef9aa2e502","groups":["system:serviceaccounts","system:serviceaccounts:cert-manager","system:authenticated"],"extra":{"authentication.kubernetes.io/pod-name":["cert-manager-648cd49b44-z6g8s"],"authentication.kubernetes.io/pod-uid":["4bd741fa-a8ec-48a1-82d5-26c5b7acce5e"]}}I0112 17:47:09.477776 1 mutation.go:113] webhooks/resource/mutate "msg"="mutation rules from policy applied successfully" "clusterroles"=["cert-manager-controller-approve:cert-manager-io","cert-manager-controller-certificates","cert-manager-controller-certificatesigningrequests","cert-manager-controller-challenges","cert-manager-controller-clusterissuers","cert-manager-controller-ingress-shim","cert-manager-controller-issuers","cert-manager-controller-orders","system:basic-user","system:discovery","system:public-info-viewer","system:service-account-issuer-discovery"] "gvk"={"group":"cert-manager.io","version":"v1","kind":"Certificate"} "gvr"={"group":"cert-manager.io","version":"v1","resource":"certificates"} "kind"="Certificate" "name"="defaults-example-certificate-tls" "namespace"="default" "operation"="UPDATE" "policy"="mutate-certificates" "resource.gvk"={"Group":"cert-manager.io","Version":"v1","Kind":"Certificate"} "roles"=["kube-system:cert-manager:leaderelection"] "rules"=["set-revisionHistoryLimit","set-privateKey-rotationPolicy","set-privateKey-details"] "uid"="c4384662-cb2a-49a0-8e83-e590942ec48d" "user"={"username":"system:serviceaccount:cert-manager:cert-manager","uid":"21cbad67-9d2e-44ee-bb02-7fef9aa2e502","groups":["system:serviceaccounts","system:serviceaccounts:cert-manager","system:authenticated"],"extra":{"authentication.kubernetes.io/pod-name":["cert-manager-648cd49b44-z6g8s"],"authentication.kubernetes.io/pod-uid":["4bd741fa-a8ec-48a1-82d5-26c5b7acce5e"]}}Taking the last line as an example you can pull out:
"kind"="Certificate" "name"="defaults-example-certificate-tls" "namespace"="default" "operation"="UPDATE" "policy"="mutate-certificates" "resource.gvk"={"Group":"cert-manager.io","Version":"v1","Kind":"Certificate"} "roles"=["kube-system:cert-manager:leaderelection"] "rules"=["set-revisionHistoryLimit","set-privateKey-rotationPolicy","set-privateKey-details"]See the
policy
key indicates that our policy has been applied. In therules
section you can identify that three of our five rules have been applied to the generated "defaults-example-certificate-tls"Certificate
resource.
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:
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
.
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 simple ClusterPolicy
with only 5 rules can change the user experience creating Certificate
resources from:
apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: test-revision-overridenamespace: defaultspec:dnsNames:- example.comissuerRef:group: cert-manager.iokind: ClusterIssuername: not-my-corp-issuerprivateKey:algorithm: RSAencoding: PKCS8rotationPolicy: Neversize: 4096revisionHistoryLimit: 44secretName: test-revision-override-cert
🔗 cert-test-revision-override.yaml
To instead only need to specify the configuration important to them, for example:
apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: test-minimalnamespace: defaultspec:dnsNames:- example.com
With this policy we achieved our objective and have enabled users to submit minimal Certifiate
resources.
This completes our fifth use case, 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.
Cleanup
If you created the kind cluster for this tutorial you can simply run:
kind delete cluster --name defaults
Otherwise to remove all resources deployed in this tutorial:
# Assuming you are running from this directly or saved all the files to yamls/kubectl delete -f ingress.yamlkubectl delete -f cpol-mutate-certificates-1.yamlhelm uninstall kyverno -n kyverno-systemhelm uninstall cert-manager -n cert-managerhelm uninstall ingress-nginx -n ingress-nginx
Appendix
cert-manager version requirement
The behavior 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. Instructions for a manual fix can be found in this comment on PR #6311.
Presets Feature Request
For further background reading around setting "defaults" or "presets", you can refer to issue 2239. This tutorial came out of an investigation of that issue.
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 as an alternative tool.