Kubernetes is now the standard for container orchestration. With organizations slowly adopting container first development structure, a large part of existing workloads are still running on virtual machines, either in the public cloud or private data centres. Many companies are now faced with migrating from their previous methods to Kubernetes.
Migrating to Kubernetes touches the entire devops process, including monitoring, logging, CI/CD, and most importantly, security. Security can be handled both at the cluster level and also at the application level.
In this post, we will try to gain more insight into how we can manage application secrets effectively in Kubernetes.
In Kubernetes, sensitive information such as API integration tokens, OAuth tokens, and database passwords are managed by a secret object. These secrets are made accessible to pods as a mounted volume.
If you are running different Kubernetes clusters for different environments (which is highly recommended) you might want to store all your environment specific secrets in a single place. Then, make sure you have a secret management tool which can smartly identify the environment the pod is deployed in and fetch secrets accordingly. We will learn more about doing this later in this post.
Let’s create a secret for a token to be used by an application for authenticating with a 3rd party service.
We can either create a secret from a literal value or a file. In this case we have put our secret information in a file named access.txt.
Let’s now use this secret in your pod as a mounted volume.
First, we create a demo pod and apply the manifest.
Now, if we exec inside this pod, we should be able to find our secret mounted to /tmp directory.
We can use this method to safely mount configuration files which contain sensitive data. Then, they can be read by the application from the mounted directory. However, sometimes we also want sensitive data to be available as env var for our application. Let’s try to do that in the next section.
In order to make secrets available as env var, we will create a secret object manifest and apply it. We will first encode our data before putting it in the secret file as plain text.
In order to inject the secret as an env var we will have to access the secret using the key at which it is stored and map it to an env var for the pod. In the following example we do this with another demo pod, demo-pod-2.
Now, if we exec into demo-pod-2 and check the value for APP_AUTH_TOKEN env var, we should see the decoded value of our secret.
Recently Kubernetes came out with a new feature for encrypting secrets at rest. I highly recommend reading it. The feature is quite new, but that shouldn’t stop us from trying it out.
So far, we have learned about how Kubernetes secrets work and how we can consume them in a pod. However, when we are running multiple Kubernetes clusters (development/staging/production) we need a centralized secret store and a secure mechanism to populate our pod environment with the required secrets.
Today we will explore two solutions for this:
The workflow for Vault based authentication can be summarized as follows:
Furthermore, in order to inject those secrets as environment variables into the pod we have a few options, which we will talk about later on.
Vault is a lightweight tool for effectively storing and managing secrets. It has great support for Kubernetes authentication, which is totally encrypted and secure. Vault is usually backed by Consul as the storage engine. Therefore it is highly reliable and resilient to node failures.
We have a couple of ways to set up Vault for our Kubernetes applications:
How to set up Vault on Kubernetes is beyond the scope of this blog, but below are some resources which you can use:
Once you have Vault set up, it is possible to hook it up with your Kubernetes cluster. As stated previously, the goal is to make sure your pods can effectively authenticate with Vault, so the application can retrieve the secrets.
First, create a Kubernetes service account with the following permissions.
Then, retrieve variables to enable Kubernetes auth in the Vault backend.
Kubernetes host — an address that Vault can connect with.
Cluster authority data — a certificate to verify the connection.
User token — a vault-auth service account with token reviewer role. Vault will interact with the cluster using this service account.
Once we have all of these values, we can enable Kubernetes auth in our Vault backend.
Now we need to make sure that our newly launched pods can authenticate with our Vault server. For this, we bind a namespace to our Vault role and will later on use the service account JWT in that namespace for authentication.
Let’s test it!
Create a demo pod and try to authenticate with Vault.
We can now use VAULT_TOKEN to authenticate with Vault and retrieve our secrets. However, this is only half the battle. Our goal was to populate the environment variables with Vault secrets so that the application can consume them.
If your application has logic to read directly from Vault using the VAULT_TOKEN and VAULT_ADDRESS environment variables then you can completely skip this part. Otherwise you can do the following:
It is a highly scalable and secure service provided by AWS to store your secrets. You can read and write secrets using AWS CLI. If you are accessing it from an EC2 instance or an ECS task, appropriate IAM roles should be configured.
Chamber is a command line utility which helps you read and write secrets from AWS Parameter Store. It supports a number of other commands to populate the execution environment with the secrets or to export them in various formats.
In order to get started with this you will need the following information handy:
If you’re on a Mac, Chamber can be installed locally using the following commands.
Make sure you have AWS CLI configured on your laptop.
Use the following to write secrets to AWS Parameter Store.
Use the following to read secrets from the AWS Parameter Store.
To integrate Chamber with your Kubernetes apps you will need to make some minor changes to the following:
At the top your Dockerfile, add the following content.
This will build and integrate the Chamber binary into your container.
In the entrypoint script, make the following changes.
Before the main entrypoint logic kicks in, add the following statement to populate the environment variables.
The following environment variables need to be set in the manifest for the deployment.
Chamber also needs access to AWS_SECRET_KEY_ID and AWS_SECRET_ACCESS_KEY, however we do not recommend setting those in your pod manifest file. It is highly recommended that you use an IAM role based permission method to authenticate with AWS Parameter Store. In order to do that, make sure the IAM role assigned to your Kubernetes worker nodes has ssm:GetParameters action enabled.
Once you have all this set up, the container in your pod will read the secrets under the SERVICE environment variable. Then, it will authenticate with AWS Parameter Store to populate the container’s environment with all of the secrets needed for that service.
Kubernetes secrets, if managed correctly, can extremely simplify the deployment process. You can choose to inject them in your application’s execution environment or read them on the fly using custom built logic.
If you have deployed your Kubernetes cluster in AWS cloud, we highly recommend using the Chamber and AWS Parameter Store integration as it is easiest to get started with and very secure.
Also, you can manage access to Parameter Store using fine grain IAM access with the help of Kiam. This will make sure only certain pods in your cluster can retrieve and use secrets from AWS Parameter Store. However, that is a discussion for another time.