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