Photo by Franck / Unsplash
Photo by Franck / Unsplash

How to Secure K8S Nginx Ingress With Let’s Encrypt and Cert Manager

Automate the provisioning of Let's Encrypt certificates for ingress resources

Guillaume Vincent
Guillaume Vincent

Kubernetes ingress exposes a web application or a REST API within a cluster to the outside. However, the application is reachable over HTTP by default which is not secure. The traffic between the application and clients is not encrypted.

The ingress can be configured to serve the application in HTTPS. It works like a reverse proxy by offloading this functionality from the application. To do this, you need to configure a certificate in the ingress specifying it in the tls field:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wordpress
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wordpress
                port:
                  number: 80
  tls:
    - hosts:
      - example.com

By doing this, the certificate will not be trusted by browsers and visitors will get a warning. This may reduce the user experience and the traffic of an application. To avoid that it will be necessary to generate valid certificates and especially from a certification authority.

Untrusted HTTPS certificate warning in the browser
Untrusted HTTPS certificate warning in the browser

The purpose here is to detail to you how to automate the lifecycle of certificates with Cert-manager and Let's Encrypt as a certification authority. Cert-manager automates the provisioning of  HTTPS certificates within your Kubernetes cluster. It provides custom resources to simplify the obtain, renewal, and use of those certificates.

In order we will see how to :

  • install cert-manager in your k8s cluster
  • create certificate issuers for Let's Encrypt
  • expose a WordPress blog over HTTPS using the Nginx ingress controller

Installing Cert-Manager

Cert-manager is easy to install using Helm. Helm is a Kubernetes package manager that allows you to add applications to your cluster using repositories with pre-built charts.

The first step is to add the Jetstack repository:

$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update

Install Cert-Manager with CRDs into your cluster:

$ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true

Jetstack also offers a kubectl plugin to easily manage cert-manager resources in your cluster:

$ curl -L -o kubectl-cert-manager.tar.gz https://github.com/jetstack/cert-manager/releases/download/v1.6.1/kubectl-cert_manager-darwin-amd64.tar.gz
$ tar xzf kubectl-cert-manager.tar.gz
$ sudo mv kubectl-cert_manager /usr/local/bin

I recommend you install cmctl for a better experience via tab auto-completion:

$ curl -L -o cmctl-darwin-amd64.tar.gz https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cmctl-darwin-amd64.tar.gz
$ tar xzf cmctl-darwin-amd64.tar.gz
$ sudo mv cmctl /usr/local/bin
$ kubectl cert-manager help                                               

kubectl cert-manager is a CLI tool manage and configure cert-manager resources for Kubernetes

Usage: kubectl cert-manager [command]

Available Commands:
  approve      Approve a CertificateRequest
  check        Check cert-manager components
  convert      Convert cert-manager config files between different API versions
  create       Create cert-manager resources
  deny         Deny a CertificateRequest
  experimental Interact with experimental features
  help         Help about any command
  inspect      Get details on certificate related resources
  renew        Mark a Certificate for manual renewal
  status       Get details on current status of cert-manager resources
  version      Print the cert-manager CLI version and the deployed cert-manager version

Flags:
  -h, --help                           help for kubectl
      --log-flush-frequency duration   Maximum number of seconds between log flushes (default 5s)

Use "kubectl cert-manager [command] --help" for more information about a command.

For the rest of this tutorial, you will also need to install the Nginx ingress controller:

$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm update
$ helm install ingress-controller ingress-nginx/ingress-nginx

Configure The Let's Encrypt Certificate Issuer

Issuers and cluster issuers are resources supplying certificates to the cluster. The Cert-Manager installation is unable to issue certificates. You need to configure an issuer for Let's Encrypt to dynamically acquires new certificates for your services:

cert-manager presentation diagram from https://cert-manager.io/docs/
cert-manager presentation diagram from https://cert-manager.io/docs/

ClusterIssuer resources are cluster-based while Issuer are namespace-based

We are going to set up a cluster issuer for staging and production usage. It is good to prefer staging while you configure your integration. It avoids hitting the Let's Encrypt production rate limit.

In the following cluster issuer manifests you need to replace the email address with yours

Staging

Create a YAML file named letsencrypt-staging.yaml:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
      - http01:
          ingress:
            class: nginx
letsencrypt-staging.yaml
$ kubectl create -f letsencrypt-issuer-staging.yaml

Production

Create a YAML file named letsencrypt-production.yaml

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-production
    solvers:
      - http01:
          ingress:
            class: nginx
letsencrypt-production.yaml
$ kubectl create -f letsencrypt-issuer-staging.yaml

Obtain an HTTPS Certificate

The issuer is now in place and ready to retrieve a certificate for the services exposed by an ingress resource. Cert-manager monitors ingress resources and creates certificates based on the tls field.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
        - name: wordpress
          image: wordpress:latest
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress
spec:
  selector:
    app: wordpress
  ports:
    - protocol: TCP
      port: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wordpress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wordpress
                port:
                  number: 80
  tls:
    - hosts:
      - example.com
wordpress.yaml

The YAML file above defined a deployment creating one pod, a service, and an ingress exposing the service. The ingress uses the Nginx controller deployed previously.  The pod runs a WordPress container that will be reachable over HTTPS.

Cert-manager detects the annotation cert-manager.io/cluster-issuer in the ingress resource. It will use letsencrypt-staging cluster issuer to acquire a certificate for the hostname defined in the tls hosts field.

Once the acquisition of a certificate is validated with the staging issuer you can proceed to the production issuer. Staging certificates are valid but are not trusted by browsers. You have to replace the value of the ingress resource field cert-manager.io/cluster-issuer with letsencrypt-production

Conclusion

We have seen how to expose a web application in a secure with HTTPS and an ingress. It is possible now to set up trusted and valid certificates for the cluster endpoints on the fly. There is just needed to add a simple annotation in the ingress resources. Cert-Manager monitors the ingress resources with this annotation and automates the provisioning of a valid Let's Encrypt certificate. It also deals with certificate renewal.

Cloud-Native

Guillaume Vincent Twitter

DevOps Engineer & AWS Certified Solution Architect. Cloud enthusiast and automation addict