Installing and using Cert-Manager in Kubernetes

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.

Installation

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: user@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: user@example.com
    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:            user@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:  user@example.com
    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.

Further reading

Leave a Comment