Implementing TLS in Kubernetes
SnykSec
Posted on August 1, 2023
As cloud technology continues to evolve, the demand for Kubernetes is skyrocketing. As a result, security has become a top priority for developers looking to protect their application data. That's where Transport Layer Security (TLS) comes into play. TLS is essential for ensuring a secure connection between your applications and the internet.
TLS leverages asymmetric and symmetric cryptographies to keep your data secure in transit and at rest. The TLS certificate used in Kubernetes is typically a public key certificate issued by a trusted certificate authority (CA). This certificate contains information about the server's identity, the public key used for encryption, and the digital signature of the CA that issued the certificate.
This article will guide you through the implementation of an TLS connection for your applications on a Kubernetes cluster, providing you with a comprehensive understanding of TLS and its importance in securing your applications and data.
How TLS is implemented for Kubernetes workloads
When incoming requests come to an application running in a Kubernetes cluster, they go through a network component serving as a gateway for the incoming traffic. Depending on how you configure your application, this network component can be a Kubernetes Ingress controller, a load balancer service, a cluster IP service, or a node port service.
Kubernetes does not provide a default implementation of TLS encryption for the communication between the client and the network component. This means that requests are served through HTTP, not HTTPS. However, you can easily configure your Kubernetes app to use HTTPS for secure communication using TLS.
Kubernetes also provides features such as Secrets and ConfigMaps, that can be used to securely manage your TLS certificates and other sensitive data required by your application.
Historically, TLS (and its predecessor, SSL) was implemented between systems and users that required secure communications. As distributed systems have proliferated along with a massive reduction in the cost and preformance impact of implementing TLS — not to mention ever-growing privacy concerns — most experts now recommend all traffic be encryted. Some common use cases for network encryption include the following:
- Payment systems: If your application deals with payment data, such as bank accounts and credit cards, you have to implement TLS. This is a regulatory requirement and is necessary to ensure user data is safe in transit. Encrypting payment data also instills confidence in your customers and ensures they feel secure when using your services.
- End-to-end data encryption with a service mesh: Using an end-to-end data encryption mechanism with a service mesh like Istio, TLS can secure communication between different microservices within a Kubernetes cluster. This is a popular approach for modern, distributed microservice architectures.
- IoT and edge computing: Kubernetes applications interacting with IoT devices or edge computing systems should implement TLS to secure data transmission between devices and the cloud, preventing unauthorized access to data generated by IoT devices.
- Content delivery networks (CDNs): If your applications use CDNs for caching and delivering content, implementing TLS can help you ensure secure transmission of content and prevent data tampering or unauthorized access.
Implementing a TLS configuration on Kubernetes
In the following sections, you'll implement TLS for a simple Kubernetes application using a Node.js project that mocks a payment processing application. The application code and necessary Kubernetes configuration are available in this GitHub repository.
Prerequisites
To follow along with this tutorial, you need the following:
- A container runtime engine: For this article, we will assume you have Docker installed and configured on your workstation.
- A Kubernetes distribution: You need to install a Kubernetes distribution to create the Kubernetes cluster and other necessary resources, such as deployments and services. This tutorial uses kind (v0.18.0), but you can use any other Kubernetes distribution, including minikube or K3s.
- kubectl: This command line interface is used to communicate with your Kubernetes cluster and issue commands. This tutorial uses version 1.26.
-
mkcert: This is used to obtain a trusted TLS certificate with a custom domain name for your development machine. You can install
mkcert
on your development machine following the official instructions. - Git and Helm: You need Git installed to use the demo project locally and the Helm package manager for Kubernetes. This guide uses Helm v3.11.2.
The following sections will walk you through implementing TLS for a demo payment application deployed to a local Kubernetes cluster. You initially deploy the app to your kind cluster and verify that it uses HTTP instead of HTTPS. Then, you’ll generate a self-signed TLS certificate and configure your payment app to use that. Finally, you’ll create a trusted TLS certificate using mkcert
and verify that your Kubernetes app can be accessed over HTTPS.
Deploy the payment app to Kubernetes
To start the process of implementing TLS in your Kubernetes-based sample application, you need to clone the application to your local machine by running the following command:
git clone https://github.com/snyk-snippets/tls-in-k8s.git
Then change into the project directory using this command:
cd k8s-tls-demo
Once you're in the project directory, you can deploy the application to your Kubernetes cluster.
Use the following command to create your kind cluster:
kind create cluster --config=config.yaml
Note: The config.yaml
file binds a couple of ports on the kind nodes to your localhost, this is nessesary on Docker Desktop instalations as the kind containers are actually running inside a virtual machine (VM). If you are running on a Linux workstation with docker natively installed (no Docker Desktop VM), you can omit the --config
option here.
Next, we will build the container image and load it into our kind cluster.
docker build -t demo-payment-app .
kind load docker-image demo-payment-app
Note: optionally, you could push that image to a registry instead of using kind load
, if you do so, you will also need to edit the deployment.yaml file (used below) with your registry information and update the imagePullPolicy
to “Always” so it will pull that image down.
Once the cluster is created, deploy the application to your cluster with these commands:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
Wait a few minutes for Kubernetes to create the pods and other necessary resources. You can check the status of your resources using these commands:
kubectl get deployments
kubectl get pods
Your demo project exposes the payment application using a nodePort
service on port 30080
. When you define a Kubernetes service without specifying the type field, Kubernetes assigns the default service type — ClusterIP
.
However, the IP address exposed by ClusterIP
is only accessible from within the cluster's network. This means you can't view the application running in your cluster using an external client like your web browser. In production, you might want to use a LoadBalancer
service so your cloud provider can provide an external load balancer with a publicly accessible IP address.
Use these commands to see the services for this demo application:
kubectl get services
Depending on your runtime environment, you can access the demo application in one of two ways:
VM based (i.e. Docker Desktop):
To access the demo application from your browser, you will use the kind port binding set up in the config.yaml
file and simply use localhost:30080
.
Non-VM based (i.e. Linux w/out Docker Desktop):
To access the demo application from your browser, you need to find the IP address exposed by the nodePort
with the following command:
kubectl get nodes -o wide
This should display the INTERNAL-IP
of your Kubernetes control plane (ie 172.18.0.2
). Now, you can access the payment application using the following:
http://172.18.0.2:30080
Make sure to replace the IP address with the actual INTERNAL-IP
of your Kubernetes control plane or localhost
per above directions.
Opening your browser to the above IP and port brings up the Payment Page where you can enter the card information. Because this is a mock application, there's no actual processing behind this page and the data isn't saved anywhere:
Depending on your browser, you should see some kind of indication that the page is Not secure near your URL bar, indicating that this application is being served through HTTP, not HTTPS. You can verify this by clicking the prompt:
Implement a self-signed TLS certificate for your app
Now, implement TLS for the sample Kubernetes application. Create a self-signed certificate and private key:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=my-demo-app.local"
Then create a Kubernetes Secret containing the certificate and private key:
kubectl create secret tls my-demo-app-tls --key tls.key --cert tls.crt
Now, you need to install the Nginx Ingress Controller so that it can redirect incoming requests to your payment app to use HTTPS. Since you've exposed the app using nodePort
, you need to install the Ingress using a custom value file that specifies the service type to NodePort
.
Create a file called ingress-values.yaml
with the following content:
controller:
service:
type: NodePort
nodePorts:
http: 32080
https: 32443
Then install the Nginx Ingress Controller using Helm and the custom values file:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx -f ingress-values.yaml
Create an ingress.yaml
file with the following content:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-payment-app-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- my-demo-app.local
secretName: my-demo-app-tls
rules:
- host: my-demo-app.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-payment-app
port:
number: 80
And apply this manifest file by using the following command:
kubectl apply -f ingress.yaml
Update your /etc/hosts
file with the following entry:
my-demo-app.local
Make sure you replace with 127.0.0.1 if you are running Docker Desktop, or the IP address you retrieved from running kubectl get nodes -o wide
if not.
Now you can access the payment app using HTTPS by going to https://my-demo-app.local:32443
from your browser. When you visit the app for the first time, your browser should display a warning page, allowing you to accept the risk and proceed. After you do this, you can access the site with HTTPS:
Your browser displays this error because the TLS certificate is self-signed. This is evident from the fact that the HTTPS part of the URL is crossed out.
Implement a trusted TLS certificate for your app
To get around this, you can use mkcert
to create a trusted certificate for your development environment. To do so, create a local CA and generate a trusted certificate for your local domain:
mkcert -install
mkcert my-demo-app.local
Delete the previous Secret and create a new one for mkcert
:
kubectl delete secret my-demo-app-tls
kubectl create secret tls my-demo-app-tls --key
my-demo-app.local-key.pem --cert my-demo-app.local.pem
Finally, redeploy the Ingress resource by running the following:
kubectl delete ingress demo-payment-app-ingress
kubectl apply -f ingress.yaml
Verify the TLS for your Kubernetes application
At this point, you should be able to visit your payment app with a trusted TLS certificate. Restart the browser, then go to https://my-demo-app.local:32443 so that the new certificate can work. You should see that there's no warning this time, and the HTTPS part of the URL isn't crossed out, just like normal TLS-secured websites:
Click on the lock icon in the URL bar to verify that the TLS certificate is trusted by your browser and that the connection is secure:
You can find the updated code examples on the solution
branch of our GitHub project repository.
Conclusion
When working with sensitive data, TLS provides you with the security you need. However, implementing TLS can be challenging if you don't know what to do.
In this tutorial, you learned how to implement TLS for an application deployed to a local Kubernetes cluster. You can also use these principles to implement TLS for applications hosted on-premise or in a cloud provider.
When implementing TLS for a production system, you should consider using a public certificate from a trusted CA, such as Let's Encrypt. While this requires that you have access to your site domain, you can implement additional configurations, such as using a load balancer service to expose your application or managing multiple certificates through a certificate manager like cert-manager. The advantages of exposing your application through a load balancer include improved availability, scalability, and resilience. And using a certificate manager makes the provisioning and management of your cluster certificates effortless in the future.
Posted on August 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.