Make your WebApp running on kubernetes available on HTTPS with a TLS certificate.

Hello,
today i’ll walk you through by a tutorial i wrote on how to be able to get to your web application on HTTPS.
that will require generating a TLS certificate and uploading it to kubernetes to be used as a secret mounted as a volume to the pod.
the web application is running on a kubernetes cluster with RKE engine, it is a simple NGINX, just to make a POC.

Prerequisites:

          A kubernetes cluster with ingress controller installed.
          Load Balancer component on kubernetes cluster.

here is a tutorial of how to install kubernetes cluster:    https://eli-bukin.com/projects/singlenode-rke-cluster-installation/
and this is a tutorial of how to install a Load Balancer on kubernetes:  https://eli-bukin.com/projects/deploy-metallb-load-balancer-on-kubernetes/

What Is An SSL/TLS Certificate?

An SSL/TLS certificate is a digital object that allows systems to verify the identity & subsequently establish an encrypted network connection to another system using the Secure Sockets Layer/Transport Layer Security (SSL/TLS) protocol. Certificates are used within a cryptographic system known as a public key infrastructure (PKI). PKI provides a way for one party to establish the identity of another party using certificates if they both trust a third-party – known as a certificate authority. SSL/TLS certificates thus act as digital identity cards to secure network communications, establish the identity of websites over the Internet as well as resources on private networks.

Why are SSL/TLS certificates important?

SSL/TLS certificates establish trust among website users. Businesses install SSL/TLS certificates on web servers to create SSL/TLS-secured websites. The characteristics of an SSL/TLS-secured webpage are as follows:

A padlock icon and green address bar on the web browser
An https prefix on the website address on the browser
A valid SSL/TLS certificate. You can check if the SSL/TLS certificate is valid by clicking and expanding the padlock icon on the URL address bar
Once the encrypted connection has been established only the client & the webserver can see the data that is sent.

This is your Startpoint, you already deployed your beautiful webapp on your shiny kubernetes cluster, and it’s running…. it is very initial at this point, you have your app (nginx), and you have your service of a LoadBalancer type, that’s it, only those two resources in your namespace.
You are in a state where the app is deployed in “aaa-sandbox-01” namespace, you can CURL on port 80 and get response.

This is your ‘nginx-deployment.yaml’ file.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: aaa-sandbox-01
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx-service
  namespace: aaa-sandbox-01
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  externalTrafficPolicy: Local
  loadBalancerIP: 192.168.66.133
  type: LoadBalancer

The additional components that has to be added are: 

               Sercret to hold the certificate and the key,

               Custom config file for NGINX,

               Config Map to be able to inject your configuration to NGINX,

               Ingress to define rules and point the dns to the service.

Let’s first start with creating the secret,
for that matter you will have to generate a self signed certificate, let’s use openssl for that.

I’ll go over the comand:

               Openssl:      the tool that is used to create the certificate.

               Req:     means you request a certificate.

               -x509:     is the type of the certificate.

               -newkey:     meaning you creating a new key.

               Rsa:4096:     is the type and the length of the key.

               Sha256:     is the algorithm used to create the certificate.

               -nodes:     no password protection on the key.

               -keyout tls.key:     is the key specified.

               -out tls.crt:     certificate output.

               -subj “/CN=my-shiny.app” days- 365:     is used so it wont go in to interactive mode, so just specify the subject here.

openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout tls.key -out tls.crt -subj "/CN=my-shiny.app" -days 365

As you can see two files were created tls.crt and tls.key, now is the time to get those files to kubernetes in form of a secret, run the following command to create a secret of TLS type:

k create secret tls my-shiny-app-tls --cert=tls.crt --key=tls.key -n aaa-sandbox-01

Let’s take a peek into the secret to see that you have the crt and the key.

k get secret my-shiny-app-tls -n aaa-sandbox-01 -o yaml

Next let’s proceed with the NGINX config file, create a new file named ‘default.conf, and use the following block:

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;
    listen 443 ssl;
    root /usr/share/nginx/html;
    index index.html;
    server_name localhost;
    ssl_certificate /etc/nginx/ssl/tls.crt;
    ssl_certificate_key /etc/nginx/ssl/tls.key;
    location / {
            try_files $uri $uri/ =404;
    }
}

Now add a Config Map to store configuration data separately from the POD, add the following block to your ‘nginx-deployment.yaml’ file.
As you can see, this configuration sets up an Nginx server with two virtual hosts:
One listening on port 80 with the server name my-shiny.app, serving content from /usr/share/nginx/html.
Another listening on port 443 with SSL enabled, also with the server name my-shiny.app, serving content from /usr/share/nginx/html. This server expects TLS certificates located at /etc/nginx/certs/tls.crt and /etc/nginx/certs/tls.key.

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
  namespace: aaa-sandbox-01
data:
  nginx.conf: |
    events {
      worker_connections 1024;
    }
    http {
      server {
        listen 80;
        server_name my-shiny.app;
        
        location / {
          root /usr/share/nginx/html;
          index index.html;
        }
      }
      server {
        listen 443 ssl;
        server_name my-shiny.app;
        ssl_certificate /etc/nginx/certs/tls.crt;
        ssl_certificate_key /etc/nginx/certs/tls.key;
        
        location / {
          root /usr/share/nginx/html;
          index index.html;
        }
      }
    }

Now let’s create an ingress, add the following block to your ‘nginx-deployment.yaml’ file.

this Ingress configuration sets up routing rules to forward incoming HTTP and HTTPS requests for the host my-shiny.app to the nginx-service, which is listening on ports 80 and 443, respectively. TLS termination is configured for HTTPS requests using the TLS secret named my-shiny-app-tls.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: aaa-sandbox-01
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  tls:
    - hosts:
        - my-shiny.app
      secretName: my-shiny-app-tls
  rules:
    - host: my-shiny.app
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-service
                port:
                  number: 80
    - host: my-shiny.app
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-service
                port:
                  number: 443

Aditionally you have to update the ‘Service’ block that you started with and add HTTPS port.

apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx-service
  namespace: aaa-sandbox-01
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https            # <-- add this
    port: 443              # <-- add this
    protocol: TCP          # <-- add this
    targetPort: 443        # <-- add this
  selector:
    app: nginx
  externalTrafficPolicy: Local
  loadBalancerIP: 192.168.66.133
  type: LoadBalancer

Another thing to modify is the ‘deployment’ block and add configuration that mounts a ConfigMap named “nginx-config” containing Nginx configuration onto /etc/nginx/nginx.conf and a Secret named “my-shiny-app-tls” containing TLS certificates onto /etc/nginx/certs within the Nginx container. The ConfigMap provides configuration data, while the Secret provides sensitive information like TLS certificates.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: aaa-sandbox-01
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        volumeMounts:                          # <-- add this
        - name: nginx-config                   # <-- add this
          mountPath: /etc/nginx/nginx.conf     # <-- add this
          subPath: nginx.conf                  # <-- add this
        - name: tls-secret                     # <-- add this
          mountPath: /etc/nginx/certs          # <-- add this
          readOnly: true                       # <-- add this
      volumes:                                 # <-- add this
      - name: nginx-config                     # <-- add this
        configMap:                             # <-- add this
          name: nginx-config                   # <-- add this
      - name: tls-secret                       # <-- add this
        secret:                                # <-- add this
          secretName: my-shiny-app-tls         # <-- add this

Apply the deployment and see that the relevant resources were added.

Now is the time to validate my promise…. Type ‘curl -k https://my-shiny.app ‘ to the console and see for yourself.

And from the browser.

               NOTE:    if it’s a self sighned certificate you will see that little exclamation mark on the lock.

Not Bad!

This is the complete ‘nginx-deployment.yaml’ file supposed to look like.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: aaa-sandbox-01
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
        - name: tls-secret
          mountPath: /etc/nginx/certs
          readOnly: true
      volumes:
      - name: nginx-config
        configMap:
          name: nginx-config
      - name: tls-secret
        secret:
          secretName: my-shiny-app-tls
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx-service
  namespace: aaa-sandbox-01
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https
    port: 443
    protocol: TCP
    targetPort: 443
  selector:
    app: nginx
  externalTrafficPolicy: Local
  loadBalancerIP: 192.168.66.133
  type: LoadBalancer
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
  namespace: aaa-sandbox-01
data:
  nginx.conf: |
    events {
      worker_connections 1024;
    }
    http {
      server {
        listen 80;
        server_name my-shiny.app;
        
        location / {
          root /usr/share/nginx/html;
          index index.html;
        }
      }
      server {
        listen 443 ssl;
        server_name my-shiny.app;
        ssl_certificate /etc/nginx/certs/tls.crt;
        ssl_certificate_key /etc/nginx/certs/tls.key;
        
        location / {
          root /usr/share/nginx/html;
          index index.html;
        }
      }
    }
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: aaa-sandbox-01
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  tls:
    - hosts:
        - my-shiny.app
      secretName: my-shiny-app-tls
  rules:
    - host: my-shiny.app
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-service
                port:
                  number: 80
    - host: my-shiny.app
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-service
                port:
                  number: 443