Automating SSL/TLS Certificates for Kubernetes using Cert-Manager: Hands-On Guide

Introduction

In the previous blog, we covered the basics of Cert-Manager and its role in managing SSL/TLS certificates for Kubernetes. We discussed how Cert-Manager simplifies certificate issuance, renewal, and management for Kubernetes Ingress resources.

In this blog, we will dive into a hands-on tutorial, showcasing how to automate the provisioning of SSL certificates for a Kubernetes application using Cert-Manager. We will explore different types of certificates, including self-signed certificates, certificates from a self-signed CA, and Let’s Encrypt certificates using DNS challenges.

Overview of Steps

We will follow these steps in our hands-on guide:

All the necessary YAML files used in this blog will be available in the git repository for easy reference.

Prerequisites

  • A running Kubernetes cluster (I am using a k3d cluster in this guide).
  • NGINX Ingress controller deployed for exposing applications.

In this lab, I am using a Kubernetes environment deployed locally with k3d(K3s cluster). Here’s the setup of our cluster:

Environment Setup

In this lab, I am using a Kubernetes environment deployed locally with k3d(K3s cluster). You can refer previous blog where we compared Kubernetes solutions which you can consider on the laptop.

➜ kubectl get nodes
NAME                            STATUS   ROLES                  AGE   VERSION
k3d-trinity-cluster1-agent-0    Ready    <none>                 18d   v1.30.4+k3s1
k3d-trinity-cluster1-agent-1    Ready    <none>                 18d   v1.30.4+k3s1
k3d-trinity-cluster1-server-0   Ready    control-plane,master   18d   v1.30.4+k3s1

➜ kubectl get ingressclass                                 
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       18d

Sample Application Deployment

I have deployed a sample application to demonstrate how SSL certificates can be provisioned. Click here for the resource manifests(I am using sample application from istio demo)

➜ kubectl get deploy -n demo-app
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
details-v1       1/1     1            1           7m9s
productpage-v1   1/1     1            1           7m9s
ratings-v1       1/1     1            1           7m9s
reviews-v1       1/1     1            1           7m9s
reviews-v2       1/1     1            1           7m9s
reviews-v3       1/1     1            1           7m9s

I am exposing the application via an Ingress resource as shown below:

kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/ingress_demo_app.yaml
➜ kubectl get ing -n demo-app
NAME           CLASS   HOSTS               ADDRESS       PORTS   AGE
product-page   nginx   productpage.local   172.28.0.60   80      2m17s

To confirm access to the application via HTTP:

➜ curl http://productpage.local -I
HTTP/1.1 200 OK

Currently, when we try to access the app via HTTPS, the default self-signed certificate is used by the NGINX Ingress controller:

➜ curl -k https://productpage.local -I
HTTP/2 200
date: Sun, 13 Oct 2024 23:25:47 GMT
content-type: text/html; charset=utf-8

You can observe that this certificate is not trusted because it is self-signed:

➜ curl -vIk https://productpage.local 2>&1 | grep -i "issuer\|subject"
*  subject: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
*  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate

Next, we will install Cert-Manager and automate certificate generation.

Cert-Manager Installation

We will begin by installing Cert-Manager to automatically manage SSL certificates. Cert-Manager provisions and renews certificates for Kubernetes Ingress resources automatically.

  • Step 1: Install Cert-Manager using Helm
➜ helm repo add jetstack https://charts.jetstack.io --force-update
➜ helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.16.1 -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/values.yaml

Verify that Cert-Manager is successfully deployed:

➜ kubectl get deploy -n cert-manager
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
cert-manager              1/1     1            1           81s
cert-manager-cainjector   1/1     1            1           81s
cert-manager-webhook      1/1     1            1           81s

Types of Certificates We Will Try

We will explore the following types of certificates:

Self-Signed Certificates

We start by creating a self-signed certificate.

  • Step 1: Create a Self-Signed ClusterIssuer
➜ kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/cluster_issuer_self-signed.yaml

Confirm the ClusterIssuer is ready:

➜ kubectl get clusterissuer
NAME                READY   AGE
selfsigned-issuer   True    7s
  • Step 2: Create a Self-Signed Certificate for the Application
➜ kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/certificate_demo_app-self-signed.yaml

Verify the certificate has been issued:

➜ kubectl get cert -n demo-app
NAME                      READY   SECRET                    AGE
productpage-self-signed   True    productpage.example.com   3s
  • Access the application with the self-signed certificate:
➜ curl -k https://productpage.example.com -I
HTTP/2 200

The certificate is self-signed, as expected:

➜ curl -vIk https://productpage.example.com 2>&1 | grep -i "issuer\|subject"
*  subject: CN=productpage.example.com                 
*  issuer: CN=productpage.example.com   

Self-signed certificates are useful for testing but are not recommended for production environments.

Certificates Issued by a Self-Signed CA

This method involves creating a CA certificate to issue certificates for the application.

  • Step 1: Create a Self-Signed CA Issuer
➜ kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/issuer_self-signed-CA.yaml

➜ kubectl get issuer -n demo-app                                                                                                             
NAME           READY   AGE
my-ca-issuer   True    15h
  • Step 2: Create a Certificate Issued by the Self-Signed CA
➜ kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/certificate_demo_app-self-signed-ca.yaml

Verify the certificate has been issued:

➜ kubectl get cert productpage-self-signed-ca -n demo-app
NAME                         READY   SECRET                       AGE
productpage-self-signed-ca   True    productpage123.example.com   15h
  • Access the application:
➜ curl -k https://productpage123.example.com -I
HTTP/2 200

Check the issuer of the certificate:

➜ curl -vIk https://productpage123.example.com 2>&1 | grep -i "issuer\|subject"
*  subject: CN=productpage123.example.com                         
*  issuer: CN=my-selfsigned-ca 

Certificates Issued by Let’s Encrypt(ACME)

Next, we will use Let’s Encrypt with DNS through AWS Route53 to provision production-grade certificates.

  • Step 1: Create a Secret for Route 53 Credentials

We need to provide AWS credentials to allow Cert-Manager to complete DNS validation with Route53:

➜ kubectl --namespace cert-manager create secret generic route53 --from-literal="secret-access-key=<AWS_SECRET_ACCESS_KEY>"
  • Step 2: Create a Let’s Encrypt ClusterIssuer

Configure Cert-Manager to use Let’s Encrypt to issue certificates via Route53 DNS validation:

kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/cluster_issuer_route53.yaml

kubectl get clusterissuer route53                        
NAME      READY   AGE
route53   True    4h2m
  • Step 3: Request Certificates from Let’s Encrypt

Now that the ClusterIssuer is configured, we can request a certificate for our application.

Create a Certificate Resource:

kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/certificate_demo_app-route53.yaml

Verify the certificate has been issued:

kubectl get cert productpage-route53 -n demo-app 
NAME                  READY   SECRET                     AGE
productpage-route53   True    productpage.pbandark.com   3h28m
  • Access the application:
➜ curl -Ik https://productpage.pbandark.com                                     
HTTP/2 200 
date: Mon, 14 Oct 2024 10:09:31 GMT
content-type: text/html; charset=utf-8
content-length: 2080
strict-transport-security: max-age=31536000; includeSubDomains

Check the issuer of the certificate:

➜ curl -vIk https://productpage.pbandark.com  2>&1   | grep -i "issuer\|subject"  
*  subject: CN=productpage.pbandark.com                                  
*  issuer: C=US; O=Let's Encrypt; CN=R11   

TXT record created on route53 for the domain validation:

Automate Certificate Management via Ingress Annotations

To automate SSL/TLS certificate management further, we can use Ingress annotations. So that you don’t need to create certificate resource as it will be created automatically.

➜ kubectl apply -f https://raw.githubusercontent.com/pratik705/snapincloud/refs/heads/main/Blogs/cert-manager/ingress_demo_app_route53_annotation.yaml

Verify the certificate has been issued:

wordpress/blogs/cert-manager …
➜ kubectl get ing -n demo-app                            
NAME                         CLASS   HOSTS                        ADDRESS       PORTS     AGE
product-page                 nginx   productpage.local            172.28.0.60   80        16h
product-page-route53         nginx   productpage.pbandark.com     172.28.0.60   80, 443   5h30m
product-page-route53-1       nginx   productpage1.pbandark.com    172.28.0.60   80, 443   5h27m
product-page-selfsigned      nginx   productpage.example.com      172.28.0.60   80, 443   15h
product-page-selfsigned-ca   nginx   productpage123.example.com   172.28.0.60   80, 443   15h

➜ kubectl get cert productpage1.pbandark.com -n demo-app
NAME                        READY   SECRET                      AGE
productpage1.pbandark.com   False   productpage1.pbandark.com   21s

➜ kubectl get cert productpage1.pbandark.com -n demo-app
NAME                        READY   SECRET                      AGE
productpage1.pbandark.com   True    productpage1.pbandark.com   106s
  • Check the certificate issued by Let’s Encrypt
➜ echo "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZBekNDQSt1Z0F3SUJBZ0lTQTZMMkZoOGY4TUxNQ1BiWTJadDlGY0JuTUEwR0NTcUdTSWIzRFFFQkN3VUEKTU
[...]
RuT1BsQzdTUVpTWW1kdW5yM0JmOWI3N0FpQy9aaWRzdEszNmRSSUxLejdPQTU0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" |base64 -d | openssl x509 -noout -issuer -subject
issuer=C = US, O = Let's Encrypt, CN = R10
subject=CN = productpage1.pbandark.com

Conclusion

In this hands-on guide, we successfully set up Cert-Manager in a Kubernetes environment to automate the provisioning and management of SSL/TLS certificates for an application exposed via NGINX Ingress. We explored self-signed certificates, certificates issued by a self-signed CA, and production-grade certificates from Let’s Encrypt using DNS challenges.

With Cert-Manager, you can ensure your Kubernetes applications are secured with trusted SSL/TLS certificates, providing a robust solution for managing certificates automatically.

If you have any query related to this topic, please add a comment or ping me on LinkedIn.

References

Scroll to Top