Using Kubernetes ConfigMaps securely
ConfigMaps is an API object used in Kubernetes to store data in key-value pairs. It’s essentially a dictionary that contains configuration settings. Some details you might expect to find in a ConfigMap include hostnames, public credentials, connection strings, and URLs.
A ConfigMap decouples an application’s code from the configurations, making it possible to alter them without impacting the application. For instance, it enables you to set different configurations for the development, test, and production environments.
However, it’s important to note that using ConfigMaps can present security challenges, as ConfigMaps doesn’t encrypt the data in it. Therefore, we must understand when it’s best to use ConfigMaps, and when ConfigMaps aren’t secure enough.
This article explores how ConfigMaps works, how to use ConfigMaps safely, and some use cases where we need to turn to other, more secure data storage.
ConfigMaps in Kubernetes
Before we see how to use ConfigMaps, let’s explore the technical details of how ConfigMaps work and what role they play in Kubernetes.
How ConfigMaps work
ConfigMaps store key-value pairs as plain and binary data making it different from other Kubernetes objects that use spec fields. ConfigMaps has the flexibility to store binary data as base64-encoded strings, and plain data as UTF-8 byte sequences.
The cluster can access ConfigMaps in the following ways:
- Mounting them as a data volume.
- Pods in the same Kubernetes namespace accessing them remotely.
- Separating it from Pods, and allowing other Kubernetes cluster components to use them.
Pods can use ConfigMaps as configuration files, environment variables, or command-line arguments.
When working with ConfigMaps, it’s essential to take note of their limited size — 1 MB. If we have more extensive data sets, we should consider other options for data storage, such as databases, separate file mounts, or file services.
The role of ConfigMaps
The most critical role that ConfigMaps play is making applications portable by separating the application code from configuration settings. This separation makes it possible to efficiently migrate from a development environment to a test environment and, eventually, a production environment.
Let’s look at an example of how a ConfigMap can help us while working with Kubernetes. For this demonstration, let’s assume we’re building an application locally and intend to deploy it with Managed Kubernetes Service providers.
To access the database locally, we must set the environment variable
DATABASE_HOST to localhost or 127.0.0.1. When migrating to a production environment, we must change the value of that variable to one that exposes the database component.
Using ConfigMaps securely
Before we start, here are the prerequisites for creating a ConfigMap in a local Kubernetes cluster:
In this demonstration, we explore how to create a ConfigMap in YAML and mount it as volume. This method allows us to create it the same way we create a Kubernetes resource.
Create a file named
mongo-config.yaml and add the following code:
kind: ConfigMap apiVersion: v1 metadata: name: test-configmap data: # Setting the configuration as key-value properties database: mongodb database_uri: mongodb://localhost:8080 keys: | image.public.key=554 rsa.public.key=36
Then, create a ConfigMap from the file above using the command below:
kubectl apply -f mongo-config.yaml
Now, let’s see how to consume the ConfigMap. This demonstration uses the ConfigMap created above with environment variables. To consume the ConfigMap, add an
envFrom property to the Pod’s YAML as shown in the code below:
kind: Pod apiVersion: v1 metadata: name: pod-variables-env spec: containers: - name: configmap-var image: nginx:1.7.9 envFrom: - configMapRef: name: test-configmap
In the code snippet above, we used
envFrom to access the ConfigMap information from a container.
The final step is to attach it to Pods, which we create by running the command below:
kubectl exec -it pod-variables-env sh
This makes the ConfigMap available to containers as an environment variable.
Secrets in Kubernetes
Secrets are often presented as secure alternatives to ConfigMaps, as they have a few things in common:
- They use key-value pairs to store data.
- Pods can use both of them as files in a volume.
- They can be consumed as environment variables (although this is not a good idea).
- They are object types that are accessed using an API server.
- They both have a 1MB limit and are therefore unsuitable for extensive data.
Secrets are objects designed to encrypt and store confidential data in Kubernetes. Some of the data we should store in Secrets include passwords, API keys, tokens, database connection strings, and so on. They allow for the separation of sensitive data from the application’s code. This separation mitigates the risk of sensitive data exposure that can put the cluster and the application at risk. One thing you should not use Secrets for is environment variables. As Liz Rice and Michael Hausenblasbokk say in their book, Kubernetes Security:
- env vars often get dumped to logs upon crash/errors
- kubectl describe pod exposes env var values in free text
- docker inspect exposes env var values in free text
There are several types of Secrets to choose from, depending on the kind of data we want to be kept private. The most commonly used type is
Opaque, which handles arbitrary user-defined data. For basic authentication, we use the
By default, Secrets aren’t encrypted and stored in etcd, the API’s server storage. Anyone with access to the API can access and alter secrets. To limit access to it, you have to do the following:
- Enable encryption of secret data at rest (many, if not all, managed K8 providers do this for you as an option at cluster creation time).
- Enable and configure RBAC rules to limit reading and editing.
- Limit Secrets creators with RBAC.
ConfigMaps versus Secrets
Although ConfigMaps and Secrets share similarities, they aren’t the same — and both are best suited to certain use cases defined by our security needs.
The main difference between ConfigMaps and Secrets is the confidentiality of the data contained in them. Secrets obfuscate data with base64 encoding, while ConfigMaps data is in plain text. Note that we can also store plain text in ConfigMaps as base64-encoded strings.
Another difference between the two is that there are several Secrets types in Kubernetes. We can choose the type of Secret to use based on the sensitive data we want to hide.
When to Use ConfigMaps
ConfigMaps are very effective in decoupling the application data from the configuration code. For example, let’s say we have an application container for employee data that connects to a MySQL database. If we hard code the database connection configuration to the container, it will create confusion when shifting from a development environment to a production environment. That’s because we can’t use the development database in a production environment.
A configMap can make it easy to shift our application by storing the configuration data in a separate file outside of the container. A shift in the environment would only require a simple database reference change.
You can easily display the data contained in ConfigMaps by running
kubectl describe configmaps <name>. We can also easily edit this data because it appears as plain text. While these features make it easy to change configurations based on the environment (development, test, or production), it is also unsuitable for handling sensitive data. Sensitive data should be stored in Kubernetes Secrets so that it can be encrypted and access to it limited.
When to Use Secrets
Like ConfigMaps, Secrets decouple the data from the application code. It allows us to prevent the exposure of sensitive data during Pod creation or modification.
Now, let’s turn back to our employee data example. In this scenario, the application needs a server host, port, database name, username, and password to connect successfully to the database.
Let’s compare two code snippets. The first demonstrates how a ConfigMap handles data, and the second shows how a Secret handles data.
Here’s how a ConfigMap looks:
db-configmap.yaml apiVersion: v1 kind: ConfigMap # ConfigMap data metadata: name: employee-database-conf namespace: default # Database configurations data: server.host: "10.07.11.653" server.port: "3030" db.name: employees_data
The configMap YAML file above contains the non-sensitive database connection details — server host, server port, and database name.
Here’s how a Secrets file would look:
db-secret.yaml apiVersion: v1 kind: Secret # Secret Data metadata: name: employee-database-auth namespace: default # The type of Secret type: kubernetes.io/basic-auth # Secret data stringData: username: dGhlYWRtaW4= //theadmin password: YWRtaW5wYXNz //adminpass
In the Secret YAML file above, the type of Secret is specified as basic-auth, which handles basic authorization credentials. The
stringData contains the data that should be kept private. In this case, that’s the username and password.
In the hypothetical case above, Secrets have helped us keep the username and password to the database private. It also ensures there’s no risk of exposing this information while creating or modifying the Pod.
Kubernetes config security
A ConfigMap is an API object in Kubernetes used to store non-sensitive configuration data in key-value pairs. By default, they store data in plain text, and make it possible to separate application code from the configurations. Secrets is also an API object in Kubernetes used to encrypt and store sensitive configuration data in key-value pairs. However, they reduce the risk of sensitive data exposure that can put the cluster and the application at risk.
To create a secure Kubernetes cluster that decouples application code and configurations, ensure that you store sensitive data in Secrets and non-sensitive configurations in ConfigMaps. Also, it is critical to ensure that you enable RBAC to restrict access to the ConfigMaps and Secrets. This reduces the risk of unauthorized configuration access or alteration.