Certificates, especially issues with certificates, are one of the things that are most hated among the sysadmins I know. In Kubernetes Cert-Manager can take away some of this pain by automating certificate management. In this guide I’ll show you how to install and configure Cert-Manager in Kubernetes.
I have issued a lot of certificates by hand with OpenSSL. So I really appreciate that Cert-Manager takes this work off my hands in Kubernetes.
What is Cert-Manager?
Cert-Manager can be seen as a microservice that is deployed in a Kubernetes cluster. Once deployed and configured it will take care of requesting, issuing and automatically renewing certificates for you.
How does it work?
During installation of Cert-Manager you will also install Cert-Manager specific Custom Resource Definitions (CRDs) like definitions of Certificate, ClusterIssuer and Issuer. Using these CRDs you can tell Cert-Manager what certificates it should request and where to obtain them from.
Cert-Manager can be installed either using regular Kubernetes manifests or Helm. Here I’ll show you the installation using Kubernetes manifests because is a bit easier, requires less steps and has less things to consider.
Note: If you want to install it using Helm I advise you to head over to the official installation guide.
Basically you only need one command. Everything that has to be done is contained in the manifest. Depending on the Kubernetes version you’re running you have to use one or the other command (most likely your version will be 1.16 or higher).
Note: Before installing you should check the most recent stable version at https://github.com/jetstack/cert-manager/releases and replace the version appropriately.
# For Kubernetes version 1.16 or higer $ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml # For Kubernets version 1.15 or lower $ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager-legacy.yaml
To check if the installation was successful enter:
$ kubectl get pods --namespace cert-manager NAME READY STATUS RESTARTS AGE cert-manager-756bb56c5-6bjfr 1/1 Running 0 56d cert-manager-webhook-66b555bb5-jfghq 1/1 Running 0 56d cert-manager-cainjector-86bc6dc648-8pnfc 1/1 Running 0 56d
You should see the three pods in running state.
Configuring an issuer
Before Cert-Manager can manage your certificates you have to configure an issuer that it can use. The issuer will usually be a third party that issues certificates and is trusted by other computers.
Cert-Manager supports various kinds of issuers:
- ACME – with the ACME issuer you can request certificates from Certificate Authorities that support the Automated Certificate Management Environment (ACME) protocol.
- Vault – with the Vault issuer you can get certificates from your self hosted Hashicorp Vault Public Key Infrastructure (PKI).
- SelfSigned – can issue self signed certificates. These won’t be trusted by other clients by default.
- CA – with this issuer you can create a certificate authority within the cluster. Certificate requests will be signed by the local CA. This issuer can be helpful if you want to use mutual TLS within the cluster.
In this guide we will use the ACME issuer. Using ACME you can obtain certificates from various Certificate Authorities that support the ACME protocol – most notably you can get free certificates from Let’s Encrypt with it. Meanwhile some Enterprise PKI solutions also support the ACME protocol. And also public CAs like Digicert support it.
Issuer or ClusterIssuer?
Cert-Manager supports two types of issuer definitions: ‘Issuer’ and ‘ClusterIssuer’. The ‘Issuer’ definition is valid for a single namespace, whereas the ‘ClusterIssuer’ is for the whole cluster. Depending on your requirements you might want to use the one or the other. In this guide we will use the ClusterIssuer with Let’s Encrypt.
Let’s Encrypt ClusterIssuer
We will configure two ClusterIssuers for Let’s Encrypt – using the staging and production API. For testing you should use the Let’s Encrypt Staging API, because the production API has rate limiting and will lock you out if you request too many certificates within a certain time.
To configure the ClusterIssuers you have to create yaml file and adapt your email address and the solver so it fits to the ingress controller you are using. We will put both Issuers in the same file and name it ‘letsencrypt.yml’.
--- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-staging spec: acme: # You must replace this email address with your own. # Let's Encrypt will use this to contact you about expiring # certificates, and issues related to your account. email: email@example.com server: https://acme-staging-v02.api.letsencrypt.org/directory privateKeySecretRef: # Secret resource that will be used to store the account's private key. name: staging-issuer-account-key # Add a single challenge solver, HTTP01 solvers: - http01: ingress: # if you use nginx use: # class: nginx lass: traefik --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: # You must replace this email address with your own. # Let's Encrypt will use this to contact you about expiring # certificates, and issues related to your account. email: firstname.lastname@example.org server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: # Secret resource that will be used to store the account's private key. name: staging-issuer-account-key # Add a single challenge solver, HTTP01 solvers: - http01: ingress: # if you use nginx use: # class: nginx class: traefik
To import them enter:
$ kubectl apply -f letsencrypt.yml clusterissuer.cert-manager.io/letsencrypt-staging configured clusterissuer.cert-manager.io/letsencrypt-prod configured
To check that they are working enter:
$ kubectl describe ClusterIssuer Name: letsencrypt-prod Namespace: Labels: <none> Annotations: <none> API Version: cert-manager.io/v1 Kind: ClusterIssuer Metadata: Creation Timestamp: 2020-05-08T16:25:35Z Generation: 1 Resource Version: 203187 UID: 2f04f031-174c-426b-bf0c-c6a3b7658454 Spec: Acme: Email: email@example.com Preferred Chain: Private Key Secret Ref: Name: prod-issuer-account-key Server: https://acme-v02.api.letsencrypt.org/directory Solvers: http01: Ingress: Class: traefik Selector: Status: Acme: Last Registered Email: firstname.lastname@example.org Uri: https://acme-v02.api.letsencrypt.org/acme/acct/85621721 Conditions: Last Transition Time: 2020-05-08T16:25:37Z Message: The ACME account was registered with the ACME server Reason: ACMEAccountRegistered Status: True Type: Ready Events: <none> ...
If the status looks like above the ClusterIssuer should be working.
Automatically issue certificates using an Ingress definition
To use the Let’s Encrypt Issuer with an Ingress you have to adapt your Ingress definitions:
- The annotation ‘cert-manager.io/cluster-issuer’ or ‘cert-manager.io/issuer’ must be present and must specify the name of the Issuer to use
- The TLS section must be present:
- One or more host must be present
- The secretName must be specified (the certificate will be saved there)
- Of course the specified hostnames must point to your Kubernetes Ingress.
--- apiVersion: extensions/v1 kind: Ingress metadata: annotations: # Specify which ClusterIssuer to use # cert-manager.io/issuer: acme123 # cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-staging name: web1.example.com namespace: web1-example spec: rules: - host: web1.example.com http: paths: - backend: serviceName: web1-example servicePort: 80 tls: - hosts: - web1.example.com - web2.example.com secretName: web1.example.com-tls
When the Ingress is deployed to Kubernetes Cert-Manager will pick it up and request the certificate from the ClusterIssuer. The ACME CA will then verify if the host can prove ownership of the hostname by connecting to it and checking if the challenge string can be found.
To check if the certificate was issued correctly you can use:
$ kubectl describe certificate -n test Name: web1.example.com-tls Namespace: test Annotations: <none> API Version: cert-manager.io/v1 Kind: Certificate Metadata: Creation Timestamp: 2020-12-29T07:52:12Z ... Spec: Dns Names: web1.example.com web2.example.com Issuer Ref: Group: cert-manager.io Kind: ClusterIssuer Name: letsencrypt-prod Secret Name: web1.example.com-tls Status: Conditions: Last Transition Time: 2020-12-29T07:52:41Z Message: Certificate is up to date and has not expired Reason: Ready Status: True Type: Ready Not After: 2021-03-29T06:52:40Z Not Before: 2020-12-29T06:52:40Z Renewal Time: 2021-02-27T06:52:40Z Revision: 1 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal GeneratedKey 4m56s cert-manager Generated a new private key Normal Requested 4m56s cert-manager Created new CertificateRequest resource "web1.example.com-tls-2067939407" Normal Issued 4m32s cert-manager Certificate issued successfully
Requesting certificates using the certificate resources
Another method to request certificates is to use the ‘Certificate’ resource. For this method you have to create a resource like the following and take care of the rest. For this I advise you to the official documentation.