Securing Todo Apps: Implement Kubernetes Ingress with SSL/TLS

This blog will use ingress to manage routing rules, load balance traffic, and enable SSL/TLS.
Pre-requisite
Kindly refer to the following blog series, before this for better understanding.
Todo App in K8s
Also, refer to the recent blog on the Ingress.
About SSL/TLS
SSL certificate enables encryption by using public-key cryptography.
SSL/TLS provides authentication through certificates, confirming the server's identity.
Websites use HTTPS to indicate that SSL/TLS is applied.
Self-signed certificate
A self-signed certificate is a type of SSL/TLS certificate not issued by a trusted Certificate Authority (CA) but is generated and signed by the entity that intends to use it.
These certificates are commonly used in development and testing environments.
Generate Self-signed certificate:
Using openssl
for creating and managing SSL/TLS certificates.
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -out minex.crt -keyout minex.key -subj "/C=IN/ST=Maharashtra/L=Mumbai/O=Self/OU=Learning/CN=minex.com"
Explanation:openssl req
: Initiates a certificate request generation process.-x509
: Tells openssl
to output a certificate in this standard certificate format.-nodes
: Tells openssl
not to encrypt the private keys with a passphrase.-days 365
: Certificate validity-newkey rsa:2048
: Create a 2048-bit RSA key pair, RSA is the most common public key encryption algorithm.-keyout
: Output file of private key.-out
: Output file of certificates, it contains the public key.-subj
: These options are used to pass the argument and avoid prompts while executing the command.
Letsencrypt certificate
Let’s Encrypt provides free SSL/TLS certificates to secure websites and other applications.
Certbot is the most commonly used tool for obtaining and renewing Let’s Encrypt certificates.
Command:certbot certonly --manual
, to create a certificate for a particular domain.
What is Cert-manager in K8s
Cert-manager is a popular Kubernetes-native tool for automating the management, issuance, and renewal of SSL/TLS certificates.
It simplifies obtaining and maintaining certificates for Kubernetes applications by integrating with various certificate authorities (CAs).
Cert-manager introduces CRDs like Issuer, ClusterIssuer, and Certificate to define and manage certificate-related configurations within Kubernetes.
Works with self-signed certificates, custom CAs, and public CAs like Let's Encrypt or AWS Certificate Manager.
It automatically issues and attaches certificates to Kubernetes Ingress resources, simplifying secure HTTPS setup using annotation on metadata sections in manifest files.
Install cert-manager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
Using a Self-signed certificate in Ingress
Here, we are using ClusterIssuer resources to obtain certificates from a CA.
A ClusterIssuer
is a cluster-scoped resource in cert-manager that issues certificates.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: todo-selfsigned
spec:
selfSigned: {}
Create a Certificate
resource to specify the domain names, duration, and other properties of the certificate.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: todo-selfsigned-cert
namespace: todo-ns
spec:
secretName: todo-selfsigned-tls
duration: 2160h # 90 days
renewBefore: 360h # 15 days before expiration
issuerRef:
name: todo-selfsigned
kind: ClusterIssuer
commonName: todo.minex.com
dnsNames:
- todo.minex.com
Explanation:
When using a
ClusterIssuer
, the secret (containing the certificate and private key) is created in the same namespace as theCertificate
a resource that references theClusterIssuer
.This behavior ensures that even though a
ClusterIssuer
is cluster-scoped, the secrets it generates are namespace-scoped, aligned with theCertificate
that requested them.
Create an Ingress resource and configure TLS.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: todo-ingress
labels:
name: todo-ingress
namespace: todo-ns
spec:
ingressClassName: nginx
rules:
- host: todo.minex.com
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: todo-app-svc
port:
number: 80
- path: "/health"
pathType: Prefix
backend:
service:
name: todo-app-svc
port:
number: 80
tls:
- hosts:
- todo.minex.com
secretName: todo-selfsigned-tls
Explanation:
Created the rules for the above host.
Here, we have used path-based routing and the TLS secret for the domain that we created from the certificate resources.
Note:
Create the DNS route for the domain to route it to the server.
The annotation cert-manager.io/cluster-issuer
can only be used in resources that support automatic certificate management. However, since we have already created the certificate itself, we did not use the annotation here.
Using letsencrypt certificate in Ingress
Here, we are using ClusterIssuer resources to obtain certificates from a CA.
Using ACME config to create a lets-encrypt certificate and using
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: todo-letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: app@minex.com
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: todo-letsencrypt-tls
solvers:
- http01:
ingress:
class: nginx
Explanation:
ACME is the protocol used by Let's Encrypt to issue certificates.
The HTTP-01 challenge involves creating a temporary HTTP file on the server for Let’s Encrypt to validate.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: todo-letsencrypt-cert
namespace: todo-ns
spec:
secretName: todo-letsencrypt-tls
duration: 2160h # 90 days
renewBefore: 360h # 15 days before expiration
issuerRef:
name: todo-letsencrypt
kind: ClusterIssuer
commonName: todo.minex.com
dnsNames:
- todo.minex.com
Explanation:
When a
Certificate
a resource is created and references thisClusterIssuer
:- Cert-Manager will send a certificate request to Let’s Encrypt.
Let’s Encrypt will perform an HTTP-01 challenge by checking the temporary file served on your domain.
Cert-Manager ensures the challenge is served correctly using the specified.
Ingress
.Once verified, Let’s Encrypt issues the SSL/TLS certificate, and Cert-Manager stores it in a Kubernetes secret.
Ingress resource configuration was similar to the self-signed, just needed to change the secretName
value.
tls:
- hosts:
- todo.minex.com
secretName: todo-letsencrypt-tls
The difference between self-signed and letsencrypt
Feature | Self-Signed Certificate | Let’s Encrypt Certificate |
Trust | Not trusted by default (unless manually added) | Trusted by most browsers and clients |
Cost | Free | Free |
Automation | No automation for renewal | Automatic renewal with tools like Certbot |
Use Case | Development, testing, internal services | Public-facing services |
Ease of Use in K8s | Manual creation and management | Automates the process via Cert-Manager |
Management Overhead | High (manual renewal and updates) | Low (automated renewal) |
Application setup
graph TD;
client([Client]) -...-> ingress[Ingress controller];
ingress[Ingress controller] -...-> n-ingress[Ingress resources];
cert-manager[Cert-Manager] -...-> cert-resource[Issuer and certificate];
n-ingress --> path1["/"] --> service1[app-svc:80];
n-ingress --> path2["/health"] -->service1[app-svc:80];
App -...-> service2[pg-svc:5432]
subgraph Cluster
ingress
cert-manager
subgraph Namespace
n-ingress;
cert-resource;
cert-resource -...-> n-ingress;
service1 --> pod1[app1-pod];
service1 --> pod2[app1-pod];
path1
path2
subgraph App
pod1
pod2
end
subgraph Config-map
postgres-config
postgres-init-config
end
subgraph Secret
postgres-secret
end
service2[pg-svc:5432] --> pod3[postgresql-1];
service2[pg-svc:5432] --> pod4[postgresql-2];
service2[pg-svc:5432] --> pod5[postgresql-3];
subgraph Database
pod3
pod4
pod5
end
end
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000,font-size:40px;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff,font-size:26px;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:4px,color:#326ce5,font-size:26px;
classDef outer fill:#d97706,stroke:#b86004,stroke-width:4px,color:#fff,font-size:26px;
classDef path fill:#28a745,stroke:#155724,stroke-width:4px,color:#fff,font-size:24px;
classDef arrowColor stroke:#dcdcdc,stroke-width:6px;
class n-ingress,postgres-secret,postgres-config,postgres-init-config,cert-resource,service1,service2,pod1,pod2,pod3,pod4,pod5 k8s;
class client plain;
class Cluster,Namespace,App,Config-map,Secret,Database cluster;
class ingress,cert-manager outer;
class path1,path2 path;
linkStyle default stroke:#dcdcdc,stroke-width:6px;
k8s-manifests/
├── cert-manager
│ ├── letsencryptCertificate.yaml
│ ├── letsencryptClusterIssuer.yaml
│ ├── selfsignedCertificate.yaml
│ └── selfsignedClusterIssuer.yaml
└── route
└── appIngress.yaml
Here, create either one issuer/certificate for the applications, and update the secretName
value accordingly.
Command:
kubectl apply -f letsencryptClusterIssuer.yaml
kubectl apply -f letsencryptCertificate.yaml
ORkubectl apply -f selfsignedClusterIssuer.yaml
kubectl apply -f selfsignedCertificate.yaml
Verify:kubectl get clusterissuer
kubectl get certificate
kubectl get secret
Once, the certificate is created and the secret file is generated, create ingress resources.kubectl apply -f appIngress.yaml
kubectl get ingress
Access application
To access the application whether it's working seamlessly or not.
GitHub URL
https://github.com/minex970/python-based-todo-application
Note: We also added deploy steps in the repo.
Thank you for reading…
Subscribe to my newsletter
Read articles from Neeraj Gupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
