12 min read

K8s Security: Encrypting API Traffic.

The kube-API server is an integral part of a K8s cluster. EVERY command issued using kubectl is sent to the kube-API server first before the intent behind those commands is executed against k8s objects like pods, services, deployments, etc.

The traffic exchanged between the kube-API server and other k8s components (or workloads) needs to be encrypted. If a malicious intrusion is made into this traffic, the cluster becomes insecure and user data could easily be siphoned away.

For the purpose of encrypting k8s cluster traffic, TLS certificates are utilized.

🗣️
If you are already aware of the fundamentals involved in TLS Certificates, skip to the next section. For others, since TLS Certificates are an important part of the trust mechanism in k8s (and in fact other platforms), learning a bit more about them is beneficial.

What are TLS Certificates?

Merriam-Webster defines the word 'certificates' as

a document evidencing ownership or debt.

For our context, we use certificates to show evidence that the identity we are presenting is truly ours and not someone who is not allowed to interact with the k8s cluster. Certificates ensure trust between 2 communicating parties. Using certificates, an entity is able to confirm its authenticity (and good intentions?) with the others it wants to exchange data.

TLS Certificates are created using symmetric or asymmetric cryptography.

Difference between Symmetric vs Asymmetric

In Symmetric encryption, the same secret key is used for encrypting and decrypting the messages being sent back and forth. In Figure 1 below, the Good Person encrypts their private message with a cryptographic key. The data is sent through to the server ALONG with the key. At the server's end, once the key and data are received, they will be used to decrypt the private message. Though fundamentally simple, this approach is not very effective.

Figure 1: The simple yet ineffective symmetric approach.

But why is it ineffective?

The assumption in Figure 1 is that there are ONLY 2 participants in this message exchange ... but such oversimplifications are far from true in the online wild world. We should always assume there is someone watching or at least attempting to watch (and steal) our data. The approach in Figure 1 completely breaks down if we think about the presence of an unknown sleuth, listening to our online digital message passing.

Figure 2: A hacker sniffing our network traffic can EASILY get the message and the key being sent to the server.

The Hacker can easily extract both the key and the message, as they are in transit to the server, and all our efforts to be safe online go down the tube.

To counter this seemingly obvious weakness in the symmetric crypto world, we typically use an asymmetric cryptographic mechanism. For our article, we will focus on the asymmetric cryptographic keys that are generated at the server. This approach generates 2 server keys as opposed to just one:

  • 1 of the keys is private and is not shared with ANYONE including the Good Person. The server will hold on to it and since it is not sent over the internet, Hacker will have a harder time getting to it
  • The other key is a public key which is sent over the internet to the Good Person and since it's on the internet, can be easily stolen by the Hacker.

Both keys are required to decrypt the messages being exchanged between Good Person and the server.

Figure 2: Both the Hacker and the Good Person will get the Public Key.

Once the Good Person gets the Public Key from the server, they will use it to encrypt their own key (yes, Good Person will still make their own key using the Public Key sent to him/her/they).

Figure 3: The Good Person has used the server Public Key to encrypt their own key and send it back to the server where the ONLY Private Key is stored (and is needed to access the secret message being sent over the wire).

The Hacker, true to their nature, will pick up this doubly-encrypted message from Good Person to the server but since they don't have the Private Key, there is nothing they can do to crack open the traffic and steal your information.

But didn't I read somewhere that Hacker can impersonate the server and convince me to accept their Public-Private key as the 'REAL' ones?

Yes. It is entirely possible that a Hacker can convince us they are the real server and that we should accept their Public Key to encrypt our messages and send it to them.

Figure 4: The Hacker gains our trust by impersonating the server, a technique known as IP Spoofing.

Once the key exchange depicted in Figure 4 is completed, your messages and private data are no longer safe.

How does someone like me (a Good Person) make sure I am not being IP-Spoofed?

To eliminate (or at least reduce) IP Spoofing, real servers use what is called a Certificate Authority. This authority (also called CA for short) is usually a third-party entity (like Verisign. DigiCert etc) that sell a CERTIFICATION (stamp of authentication that the real server is actually THE real server). This STAMP OF AUTHENTICATION is sent from the server to the Good Person when they first start to speak with each other. This so-called STAMP OF AUTHENTICATION includes a lot more information about the server that is generating the Public-Private key pair (such as location, name, company details, etc).

🗣️
All legit servers should always have the CA-provided STAMP OF AUTHENTICATION done PRIOR to the first time Good Person and server speak with each other.
Figure 5: The server sends a CSR to the CA and gets a signed certificate to be used with Good Person (and others like them later on).

Hacker will not be able to get the CA to give it a Signed Certificate since Hacker will try to impersonate a real server but with the wrong information on location and other facts, the CA will know it's being played and consequently not grant the Hacker anything.

🗣️
Anyone can generate a Certificate using Self-Signing (as is the case when we install k8s and have to use the ca-certificates module to generate our own certificate). However, in production scenarios, it is best to use trusted and well-known CA's.
In Private Networks, generating a CA Certificate using an internal Certificate Authority can be used to establish trust between servers and employees.

In summary, the flow of establishing client-server authentication using asymmetric cryptography and Certificate Authorities follows the sequence below:
1. Set up or buy Certificate Signing (CA) services.
2. Send a Certificate Signing Request (CSR) to the CA.
3. Receive the Signed Certificate (or STAMP) from the CA.
4. Save this Signed Cert in the client's Trusted Root Certificates list.

🗣️
Even though, not mentioned, the clients are also required to present a CA Signed Certificate to the server.

TLS Certificates used in a k8s Cluster.

A cluster is a combination of objects that are either a client, a server or both.

Figure 6: The Client-Server relationships in a k8s Cluster.

The kube-API server is the only k8s component that acts as both a server (for Scheduler, kube-proxy, and Controller Manager) and client (for kubelets on individual worker nodes and etcd).

Servers Client Keys
CA Entire cluster ca.crt and ca.key
etcd kube-API server etcd.key and etcd.crt are sent by etcd (the server)
apiserver.crt and apiserver.key are sent by kube-api (the client) to etcd
*In some cases, you may also consider making a special apiserver-etcd.crt and apiserver-etcd.key for etcd only.
kubelet kube-API server kubelet.crt and kubelet.key are sent to the kube-api server
In response, kube-api server also sends the apiserver.crt and apiserver.key to the kubelets.
*In some cases, you may also consider making a special apiserver-kubelet.crt and apiserver-kubelet.key for etcd only.
kube-api server scheduler scheduler sends the scheduler.crt and .key and receives apiserver.crt and .key from kube-api server.
kube-api server kube-proxy kube-proxy sends the kube-proxy.crt and .key and receives apiserver.crt and .key from kube-api server.
kube-api server kube-controller kube-controller sends the kube-controller.crt and .key and receives apiserver.crt and .key from kube-api server.
kube-api server External to cluster users kube-api server will send the apiserver.crt and .key and expect an admin.crt and admin.key to be provided in exchange.

How do we generate these certificates being used in the cluster?

The steps to generate certificates is fairly straight forward with minor modifications for some cluster components:

  1. Generate a private key
  2. Create a Certificate Signing Request (CSR)
  3. Send the CSR to a Certificate Authority
  4. Receive the Certificate (.crt) and Private Key (.key)

Step 1: Generate a private key

Figure 7: Use openssl to generate the private key.

name of component depends upon the component (of course). It is also advisable to look in the /etc/kubernetes/pki folder on the nodes to confirm the name to use.

Default names for a components private key are:

  • For Certificate Authority, ca.
  • For kube-API server when its 'serving' information to scheduler, kube-proxy and kube-controller, use apiserver.
  • For kube-API server when its a client to the etcd and kubelets, use apiserver or apiserver-etcd-client and apiserver-kubelet-client.
  • For scheduler, kube-proxy and kube-controller, use their component names.
  • For external client users of the cluster, the name admin is typically used.

Step 2: Create a Certificate Signing Request

Figure 8: Generating a CSR requires the private key that goes along with the final certificate.

The name of component should be the same as that used for the Private Key generation. Traditionally, naming the CSR the same as the Private Key makes finding them easier.

For the description, there are some minor component-scope nuances that should be noted. Typically the description is used to provide the name of the component for which the CSR is being generated:

  • For the CA CSR, use "/CN=192.168.0.100".
  • For scheduler, kube-proxy, kube-controller, etcd, use "/CN=system:<name of component>". For example, if we had to generate a CSR for the scheduler, we would set the description to "/CN=system:kube-scheduler".
Figure 9: An example of creating a CSR for the scheduler.
  • For kubelet, the description used is "/CN=system:node:<name of host node>"
  • For kube-api server, the approach is different than the one taken for the other k8s components. As mentioned earlier, the use of the description section is to register the exact name that the component uses in the cluster (like kube-scheduler or etcd). In the case of the kube-api server, it is known by many names. Kube-api server is also known as (referred to) Kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local and it's IP address. Fitting all these names inside the description is tricky yet if any one is missing, it won't be made a part of the final certificate that is generated at the end of the CSR flow and therefore if the missed name is ever used to refer to the kube-API server, the authentication will be failed.

So how does one then generate a certificate for kube-API in such a way that all its possible names are included?

We will first generate the key as done earlier. Then we create an openssl config file that contains all the names kube-API server is referred by.

Figure 10: Kube-API server has many aliases. They all need to be included in the final certificate being generated.

When executing the actual CSR request, the openssl.config file is passed as a parameter:

Figure 11: We can pass a openssl.conf file when trying to make all kube-api aliases kosher for the cluster.

Step 3: Finally send the CSR and the Private Key to a Certificate Authority.

Figure 12: Finally the .key and .csr are sent to the CA and a .crt is created.
🗣️
The certificates (.crt) that were generated using the process above, are called Self-Signed Certificates because we used a local CA.

Ok. I think I get the idea behind the .key to .csr to .crt generation. What I don't understand is how will the components and the cluster know about these keys?

We will now see how all these cryptographic safe guards are configued for use in the encryption of traffic between cluster components.

The first thing that one needs to realize is the fact that since these are all Self Signed Certificates and need to be added to the Trusted Root Certificates inventory on each server and client. Without this copy, the server and client components will not be able to authenticate ca.crt and ca.key

Configuring kube-api server with newly generated .key's and .crt's

kube-api server configuration is stored in the kube-apiserver.yaml file which is usually in the /etc/kubernetes/manifests folder.

Figure 13: [1] is used when kube-api is a client to etcd and [2] is when it is a client to the kubelets running on worker nodes.

As and when the keys and certificates are changed, they should be placed in the folders outlined in Figure 13.

Configuring etcd with newly generated .key and .crt files

The etcd manifest is also in the /etc/kubernetes/manifests folder.

Figure 14: [1] = etcd certificate [2] = etcd key [3] = keys and certificates in case etcd instances are distributed and [4] = the certificate authority authentication cert.

What about users of the cluster who are not part of the 'kube-something' family?

The approach used for adding new users with their own pair of key and certificates follows the same process as described earlier:

  1. A key and base-64 encoded CSR is generated using openssl command for a user.
  2. The user shares that CSR and the key with the cluster admin.
  3. The cluster admin creates a CertificateSigningRequest manifest in kubernetes.
  4. The manifest for CertificateSigningRequest can be applied using kubectl.
  5. The CertificateSigningRequest is approved.

Step 1: Generate a new key and a base-64 encoded CSR for the new user, using openssl.

Figure 15: Generate the Private Key as always.
Figure 16: Create a CSR using the Private Key.

Encode the CSR using base-64 encoding:

Figure 17: Using cat myuser.csr | base64 | tr -d "\n" will encode the CSR using base 64 encoding.
🗣️
The command in Figure 17 is $ cat myuser.csr | base64 | tr -d "\n"

Step 3: The cluster admin creates a CertificateSigningRequest manifest in Kubernetes.

Figure 18: The manifest for CertificateSigningRequest for myuser.
🎃
A detailed description of a CertificateSigningRequest is documented here.

Step 4: Use kubectl apply to the manifest.

Figure 19: Apply the manifest for the CertificateSigningRequest and confirm it did get created

Notice the CONDITION column shows the CSR to be in a Pending state. To change the state of the CSR from Pending to Approved, an explicit approval command for the CSR needs to be issued.

Step 5: The CertificateSigningRequest is approved.

Figure 20: Approved CSR will turn from Pending to Approved, Issued.
🎃
In summary:
- All k8s components require a Private Key, a Certificate from a Certificate Authority, and a CertificateSigningRequest object to get a valid authentication certificate. 

I write to remember and if in the process, I can help someone learn about Containers, Orchestration (Docker Compose, Kubernetes), GitOps, DevSecOps, VR/AR, Architecture, and Data Management, that is just icing on the cake.