Implementing TLS in Kubernetes
Tags: cloudsecurity, containersecurity, kubernetes
Published: 2023-08-01 02:00:37

As cloud technology continues to evolve, the demand for [Kubernetes](https://kubernetes.io/) 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](https://kubernetes.io/docs/concepts/services-networking/ingress/), a [load balancer service](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer), a [cluster IP service](https://kubernetes.io/docs/concepts/services-networking/service/#type-clusterip), or a [node port service](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport).


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](https://kubernetes.io/docs/concepts/configuration/secret/) and [ConfigMaps](https://kubernetes.io/docs/concepts/configuration/configmap/), 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](https://www.pcisecuritystandards.org/about_us/) 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](https://istio.io/), 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](https://nodejs.org/en) project that mocks a payment processing application. The application code and necessary Kubernetes configuration are available in [this GitHub repository](https://github.com/rubaiat-hossain/k8s-tls-demo).


### Prerequisites


To follow along with this tutorial, you need the following:


* **A container runtime engine:** For this article, we will assume you have [Docker](https://docker.com/) 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](https://kind.sigs.k8s.io/) (v0.18.0), but you can use any other Kubernetes distribution, including [minikube](https://minikube.sigs.k8s.io/) or [K3s](https://k3s.io/).
* [**kubectl**](https://kubernetes.io/docs/reference/kubectl/)**:** This command line interface is used to communicate with your Kubernetes cluster and issue commands. This tutorial uses version 1.26.
* [**mkcert**](https://github.com/FiloSottile/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](https://github.com/FiloSottile/mkcert#installation).
* [**Git**](https://git-scm.com/) **and** [**Helm**](https://helm.sh/)**:** 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](https://www.f5.com/glossary/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:


![](https://res.cloudinary.com/snyk/image/upload/v1690824249/blog-kubernetes-tls-http.jpg)
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:


![](https://res.cloudinary.com/snyk/image/upload/v1690824248/blog-kubernetes-tls-http-message-not-secure.jpg)
### 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](https://docs.nginx.com/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:


![](https://res.cloudinary.com/snyk/image/upload/v1690824248/blog-kubernetes-tls-https-not-secure.jpg)
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](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:


![](https://res.cloudinary.com/snyk/image/upload/v1690824248/blog-kubernetes-tls-https.jpg)
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:


![](https://res.cloudinary.com/snyk/image/upload/v1690824248/blog-kubernetes-tls-https-message-secure.jpg)
You can find the updated code examples on the `solution` branch of our [GitHub project repository](https://github.com/rubaiat-hossain/k8s-tls-demo).


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](https://letsencrypt.org/). 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](https://www.cncf.io/projects/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.