Securing Front-end Applications in Kubernetes with SSL/TLS

securing-front-end-applications-in-kubernetes-with-ssl/tls

If security was ever important, it is even more so today. Imagine sending a confidential letter through a public mail system where anyone can read or alter its content.

That is what happens when we transmit data online without securing it. That is where SSL/TLS come in; you can think of SSL/TLS as the security envelope of the digital world.

With the rapid rise of cloud-native applications, microservices architecture, and Software as a Service (SaaS), ensuring secure communication between front-end applications and users is more critical than ever. This is where SSL/TLS (Secure Sockets Layer/Transport Layer Security) comes into play.

In this article, we’ll explore how secure front-end applications in a Kubernetes environment, using SSL/TLS.

Implementing SSL/TLS is not only a security best practice but it is also a necessity for safeguarding your data. It would also ensure the integrity of the communication between front-end and back-end components.

Let’s begin by understanding what SSL/TLS is and why it is so important.

SSL/TLS: A Foundation of Security

SSL/TLS, or Secure Sockets Layer and its upgraded version, Transport Layer Security, are both cryptographic protocols that establish secure communication over the internet. In other words, they provide encryption and authentication mechanisms that enable sensitive data to travel securely between a client (e.g., a web browser) and a server. SSL is the older version, while TLS is the newer and more secure iteration.

For the purposes of this article, we’ll use the term SSL/TLS interchangeably.

How does it work?

picture

The load balancer is typically located outside the cluster and directs internet traffic to the Ingress controller, active within the cluster. The Ingress or edge proxy accepts the traffic, reads the information in the Ingress resource, then directs the traffic to the appropriate services and pods based on the requests. The ingress also continuously monitors pods and automatically updates load balancing rules, whenever pods are added or removed.

Prerequisites:

Before we get into configuring SSL/TLS for our front-end applications in Kubernetes, these are some assumptions to keep in mind:

  1. You have a functioning Kubernetes cluster.
  2. You have helm installed on your cluster
  3. You have a solid understanding of Kubernetes ingress concept.
  4. You have the kubectl tool set up on your system.
  5. You own or have control over a domain name.
  6. We will need SSL/TSL certificates. If you already have them, then skip steps ….. In case you don’t already have them, don’t worry; we’ll use Let’s Encrypt to obtain them.

1. Comprehending SSL/TLS in Kubernetes

Before we go any further. It is necessary for us to start by understanding the role of SSL/TLS in Kubernetes:

here

  • Encryption: SSL/TLS protocols ensure that the communication between your front-end applications and the outside world remains secure and protected. They encrypt the data exchanged between the client and server, thereby preventing unauthorized access and data tampering.

    • Authentication: Even beyond encryption, SSL/TLS also verifies the authenticity of the server. It ensures that the client communicates with the intended server and not an imposter.

Like sending mail to a verified physical address.

  • Data Integrity: SSL/TLS elevates the overall security of your applications, building trust with your users and also helping you meet compliance requirements by safeguarding sensitive data. It also guarantees that the data remains unaltered during transit, much like a wax seal protecting an unaltered letter.

2. Deploying our sample front-end application

I will be using my Dog Pic website as my sample frontend application. Below is our Kubernetes manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: doggie-web
  labels:
    app.kubernetes.io/name: doggie-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: doggie-web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: doggie-web
    spec:
      containers:
        - name: doggie-container
          image: learncloudnative/dogpic-service:0.1.0
          ports:
            - containerPort: 3000
---
kind: Service
apiVersion: v1
metadata:
  name: doggie-service
  labels:
    app.kubernetes.io/name: doggie-web
spec:
  selector:
    app.kubernetes.io/name: doggie-web
  ports:
    - port: 3000
      name: http

We will go ahead and save the above YAML file as dog-pic-application.yaml and run the command below to create the deployment and service

kubectl apply -f dog-pic-application.yaml

2.1 Deploying cert-manager

We’ll proceed by deploying the cert-manager inside our Kubernetes cluster. As the name suggests, the cert-manager specializes in handling certificates. Whenever we need a new certificate or the renewal of an existing one, the cert-manager can help us with that.

Our first step is to create a namespace for deploying the cert-manager in.

$ kubectl create ns cert-manager
namespace/cert-manager created

Next, we will add the jetstack Helm repository and refresh the local repository cache:

First we will run the command below to add it

$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories

Next, we will update our local helm repo

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
Update Complete. ⎈ Happy Helming!⎈

We can now go ahead and install the cert-manager. We will run the following Helm command to install it:

helm install 
  cert-manager jetstack/cert-manager 
  --namespace cert-manager 
  --version v0.15.1 
  --set installCRDs=true

Notice the output of our command, you will notice the message confirming that cert-manager was deployed successfully.

However, before we can use it, we need to set up either an Issuer resource or a ClusterIssuer and configure it. This resource represents a CA (Certified Signing Authority) and allows our cert-manager to issue certificates.

While a ClusterIssuer operates at the cluster level, the Issuer resource operates at the namespace level instead. For instance, while the ClusterIssuer can be used to issue certificates for any application in the cluster, regardless of which namespace it is in. An Issuer resource, on the other hand, can only be used to issue certificates for applications in the namespace in which it is created.

Our Cert-manager supports multiple issuer types. We’ll be configuring an ACME issuer type since Let’s Encrypt relies on the ACME protocol. These protocols offer various challenge mechanisms to establish and confirm domain ownership.

2.2 Challenges

Cert-manager facilitates the verification of domain ownership using two challenges in the case of the ACME protocol: the HTTP-01 challenge and the DNS-01 challenge. Read more on the Let’s Encrypt website.

  • The HTTP-01 challenge is the most common challenge. It requires you to place a file containing a token in a specific location on our server. For example http://[my-domain]/.well-known/acme-challenge/[token-file].

  • The DNS-01 challenge requires you to modify a domain DNS record. To pass this challenge, we will need to create a TXT DNS record with a specific value under the domain we want to claim. It is only logical to utilize the DNS-01 challenge, if your domain registrar provides an API for updating DNS records automatically. View the complete list of service providers that have incorporated the Let’s Encrypt DNS validation.

We will be using the HTTP-01 challenge since it is more generic than the DNS-01 challenge, which depends on your domain registrar.

Now let us proceed. Let us deploy a ClusterIssuer we will be using. Make sure you replace the email with your email address.

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: hello@thedomain.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            class: nginx
        selector: {}

Before we go any further, let us run the command below to make sure that our ClusterIssuer has been created and that our ACME account was registered with the email we provided in the ClusterIssuer as above.

$ kubectl describe clusterissuer

...
Status:
  Acme:
    Last Registered Email:  hello@thedomain.com
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/89498526
  Conditions:
    Last Transition Time:  2023-10-22T20:36:04Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    

Similarly, if we run the command kubectl get clusterissuer We will see the indication that the ClusterIssuer is ready:

$ kubectl get clusterissuer
NAME               READY   AGE
letsencrypt-prod   True    5m30s

Later on, once we have deployed our Ingress controller and set up the DNS record on our domain, we will then create a Certificate resource.

2.3 Ambassador Gateway

We will install Ambassador Gateway which is an open-source Kubernetes-native API gateway for microservices. We will use it as a reverse proxy to manage external access to services within our Kubernetes cluster.

To install Ambassador gateway, let us run the commands below. The first one will install all CRD (custom resource definitions).

$ kubectl apply -f https://www.getambassador.io/yaml/ambassador/ambassador-crds.yaml

The second command will install RBAC (Role-Based Access Control) resources and will create the Ambassador deployment.

$ kubectl apply -f https://www.getambassador.io/yaml/ambassador/ambassador-rbac.yaml

Let us now create our LoadBalancer service that will expose port 80 for HTTP traffic and port 443 for HTTPS.

apiVersion: v1
kind: Service
metadata:
  name: ambassador
spec:
  type: LoadBalancer
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: https
      port: 443
      targetPort: 8443
  selector:
    service: ambassador

Like we did before, save the YAML file above in ambassador-serv.yaml. The run the command below

$ kubectl apply -f ambassador-svc.yaml

Please note that deploying the service above will create a load balancer in your cloud providers’ account

Let us now check if our ambassador service, has been created:

$ kubectl get svc
NAME               TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)          AGE
ambassador         LoadBalancer   10.0.78.66    51.143.120.54   80:31365/TCP     97s
ambassador-admin   NodePort       10.0.65.191             8877:30189/TCP   4m20s
kubernetes         ClusterIP      10.0.0.1                443/TCP          30d 

Great job so far.

Now that we have created the external IP address, we can go to the website where we registered our domain and create a DNS record, that will point the domain to this external IP. This allows us to enter our domain http://our-domain.com in our browser and this will resolve to the IP address of the ingress controller inside the cluster.

I will be using my domain http://k8s4today.com. I will set up a subdomain, http://mydogs.k8s4today.com to point to the IP address of our Load Balancer (for example 51.140.123.54) using an A record. Irrespective of where you registered your domain, you should be able to update the DNS records. Refer to your domain registrar’s documentation for guidance on how to do that.

We will now set up our Ingress resource, so we can reach the My Dog Picture website we deployed on our subdomain (make sure you replace the dogs.k8s4today.com with your domain or subdomain name):

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: ambassador
spec:
  rules:
    - host: dogs.k8s4today.com
      http:
        paths:
          - backend:
              serviceName: doggie-service
              servicePort: 3000

With our ingress deployed, the Dog Pic website can be accessed at http://dogs.k8s4today.com.

Dog Picture

2.4 Requesting a certificate

To obtain a new certificate, we must first establish a ‘Certificate‘ resource. This resource contains the issuer reference (‘ClusterIssuer‘, which we defined before), the DNS domains for which we want certificates (‘dogs.k8s4today.com‘), and the Secret name where the certificate will be stored.

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: ambassador-certs
  namespace: default
spec:
  secretName: ambassador-certs
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - dogs.k8s4today.com

 Make sure you replace the dogs.k8s4today.com with your domain name.

Now we will go ahead and save the YAML above in cert.yaml and create the certificate using:

kubectl apply -f -cert.yaml

This command will create our resources. Now if we list the pods, we will notice a new pod called cm-acme-http-solver:

$ kubectl get po
NAME                          READY   STATUS    RESTARTS   AGE
ambassador-9db7b5d76-jlcdg    1/1     Running   0          22h
ambassador-9db7b5d76-qcwgk    1/1     Running   0          22h
ambassador-9db7b5d76-xsfw4    1/1     Running   0          22h
cm-acme-http-solver-qzh6l     1/1     Running   0          25m
doggie-web-7bf547bd54-f2pff   1/1     Running   0          22h

This pod was made by cert-manager to serve the token file as described in the Challenges part and check the domain name.

We can also look at the logs from the pod to see the values pod expects for the challenge:

$ kubectl logs cm-acme-http-solver-qzh6l
I0622 20:39:26.712391       1 solver.go:39] cert-manager/acmesolver "msg"="starting listener"  "expected_domain"="dogs.k8s4today.com" "expected_key"="iqUZlG9v1K8czpAKaTpLfL278piwf-mN4VZNvuwD0Ks.xonKHFvEQg2Ox_mI0cPM7UpCUHfu6H4aKtRcdrpiLik" "expected_token"="iqUZlG9v1K8czpAKaTpLfL278piwf-mN4VZNvuwD0Ks" "listen_port"=8089

However, because this pod is not available (not exposed), Let’s Encrypt cannot access it and perform the challenge. As a result, we must expose this pod via an ingress. This entails creating a Kubernetes Service and updating the ingress to point to the pod. We will use Ambassador’s Mapping resource to update the ingress. This resource specifies a mapping that will redirect requests with the prefix /.well-known/acme-challenge to the Kubernetes service that will be used by the pod.

apiVersion: getambassador.io/v2
kind: Mapping
metadata:
  name: challenge-mapping
spec:
  prefix: /.well-known/acme-challenge/
  rewrite: ''
  service: challenge-service
---
apiVersion: v1
kind: Service
metadata:
  name: challenge-service
spec:
  ports:
    - port: 80
      targetPort: 8089
  selector:
    acme.cert-manager.io/http01-solver: 'true'

We will save the yaml above as challenge.yaml and deploy it using kubectl apply -f challenge.yaml. The cert-manager will retry the challenge and issue the certificate.

Now we can run kubectl get cert and confirm the READY column shows True as below:

$ kubectl get cert
NAME               READY   SECRET             AGE
ambassador-certs   True    ambassador-certs   35m

Here are the steps we will take to request a certificate:

  1. Create the Certificate resource to request the certificate.
  2. Cert-manager constructs the http-solver pod (exposed via the challenge-service we developed).
  3. Cert-manager uses the issuer referenced in the Certificate to request certificates for our dnsNames from the authority (Let’s Encrypt).
  4. The authority (CA) sends the http-solver challenge to prove that we own the domains and verifies that the challenges are solved (by downloading the file from /.well-known/acme-challenge/).
  5. The issued certificate and key are stored in the secret and are referred to by the Issuer resource.

Below is a diagram to visualize the process

let's encrypt process

Configuring TLS in Ingress

To secure our Ingress we will have to specify a Secret that contains the certificate and the private key. We defined the ambassador-certs secret name in the Certificate resource we created earlier.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: ambassador
spec:
  tls:
    - hosts:
        - dogs.k8s4today.com
      secretName: ambassador-certs
  rules:
    - host: dogs.k8s4today.com
      http:
        paths:
          - path: /
            backend:
              serviceName: doggie-service
              servicePort: 3000

Under resource specification (spec), we use the tls key to specify the hosts and the secret name where the certificate and private key are stored.

We will save the above YAML as ingress-tls.yaml and apply it with

 

kubectl apply -f ingress-tls.yaml

3. Verification

We can now test our app. If we navigate to our sub-domain using https (e.g. https://dogs.k8s4today.com) we can now see that the connection is secure, using a valid certificate from Let’s Encrypt.

cert

Conclusion

Configuring SSL/TLS for your Kubernetes-deployed applications is much more than just encryption. It is about ensuring the integrity of data, establishing trust, and paving a smooth road for user interactions. Some things to keep in mind are to prioritize safety, renew certificates when necessary, and maintain a secure environment.

Today we learned how to protect our Ingress gateway to do this. We did that using the Ambassador gateway, however the same process could be followed for any other Kubernetes ingress controller.

Which ingress controller are you running in your Kubernetes cluster? You can Leave a comment below or send me a Tweet!

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
pyaction-426.0-released

pyaction 4.26.0 Released

Next Post
using-eventtarget-and-customevent-to-build-a-web-native-event-emitter

Using EventTarget and CustomEvent to build a web-native event emitter

Related Posts