Securing Todo Apps: Implement Kubernetes Ingress with SSL/TLS

Neeraj GuptaNeeraj Gupta
6 min read

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 the Certificate a resource that references the ClusterIssuer.

  • This behavior ensures that even though a ClusterIssuer is cluster-scoped, the secrets it generates are namespace-scoped, aligned with the Certificate 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 this ClusterIssuer:

    • 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

FeatureSelf-Signed CertificateLet’s Encrypt Certificate
TrustNot trusted by default (unless manually added)Trusted by most browsers and clients
CostFreeFree
AutomationNo automation for renewalAutomatic renewal with tools like Certbot
Use CaseDevelopment, testing, internal servicesPublic-facing services
Ease of Use in K8sManual creation and managementAutomates the process via Cert-Manager
Management OverheadHigh (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
OR
kubectl 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…

1
Subscribe to my newsletter

Read articles from Neeraj Gupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Neeraj Gupta
Neeraj Gupta