Frequently we would need to use passwords and sensitive data in our pods for them to work, like for example passwords to the databases or keys for our APIs. However, what is the best way to implement those? Adding the values is never a good idea, since it will allow other people to see that sensitive data that maybe they shouldn’t see.
For this reason, Kubernetes introduces Secrets which are defined as the following:
A Secret is an object that contains a small amount of sensitive data such as a password, a token, or a key. Such information might otherwise be put in a Pod specification or in a container image. Using a Secret means that you don’t need to include confidential data in your application code.
Because Secrets can be created independently of the Pods that use them, there is less risk of the Secret (and its data) being exposed during the workflow of creating, viewing, and editing Pods. Kubernetes, and applications that run in your cluster, can also take additional precautions with Secrets, such as avoiding writing sensitive data to nonvolatile storage.
Secrets are similar to ConfigMaps but are specifically intended to hold confidential data.
https://kubernetes.io/docs/concepts/configuration/secret/
Now, in order to learn how to create them and mount them, let’s image the next case scenario:
Currently, the webapp-deployment
is running with sensitive database environment variables directly embedded in the deployment YAML. To enhance security and protect the sensitive data, perform the following steps:
- Create a Kubernetes Secret named
db-secret
with the below sensitive database environment variable values:- Key:
DB_Host
, Value:mysql-host
- Key:
DB_User
, Value:root
- Key:
DB_Password
, Value:dbpassword
- Key:
- Update the
webapp-deployment
to load the sensitive database environment variables from the newly createddb-secret
Secret.
Creating the Secret with it’s values
First of all, let’s start by creating the Secret. A secret is basically an object with a name that contains one or multiple keys with values. So we can have for this example a secret for the database data with three keys for the needed details with its values. We can create it using the kubectl create secret
command. This allows to create secrets from string passed as arguments or even passed as files.
controlplane $ kubectl create secret generic db-secret /
--from-literal=DB_Host=mysql-host /
--from-literal=DB_User=root /
--from-literal=DB_Password=dbpassword
secret/db-secret created
Once we have created it we can check the content.
controlplane $ k get secret
NAME TYPE DATA AGE
db-secret Opaque 3 10s
controlplane $ k describe secret db-secret
Name: db-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
DB_Host: 10 bytes
DB_Password: 10 bytes
DB_User: 4 bytes
We can see now the secret, but it doesn’t display the content. As it’s stored in base64 we would need to retrieve first the values and then decode to make sure everything is right.
controlplane $ kubectl get secret db-secret -o jsonpath='{.data}'
{"DB_Host":"bXlzcWwtaG9zdA==","DB_Password":"ZGJwYXNzd29yZA==","DB_User":"cm9vdA=="}
controlplane $ echo 'ZGJwYXNzd29yZA==' | base64 --decode
dbpassword
After decoding it, we can see that the value for DB_Password
matches with what we specified.
Now we can see it because we have permissions to get Secrets, but other entities using Service Accounts which doesn’t have permissions to get the Secrets won’t be able to see the content.
How to mount Secrets in our Deployments
As mentioned in the case scenario, we have already a working deployment but this one has the data hardcoded in the yaml file. For security reasons, it’s much a better choice to mount and use the Secret we just created instead of writing the values there since someone who hasn’t have permissions to see Secrets will be able to see our sensitive data.
controlplane $ k get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
webapp-deployment 1/1 1 1 14m
controlplane $ k describe deploy
Name: webapp-deployment
Namespace: default
CreationTimestamp: Mon, 24 Jun 2024 09:03:46 +0000
Labels: <none>
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=webapp
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=webapp
Containers:
webapp-container:
Image: nginx:latest
Port: 80/TCP
Host Port: 0/TCP
Environment:
DB_Host: mysql-host
DB_User: root
DB_Password: dbpassword
Mounts: <none>
Volumes: <none>
Node-Selectors: <none>
Tolerations: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: webapp-deployment-b4ddb5755 (1/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 15m deployment-controller Scaled up replica set webapp-deployment-b4ddb5755 to 1
Inside of the YAML we would see:
controlplane $ k edit deploy webapp-deployment
....
containers:
- env:
- name: DB_Host
value: mysql-host
- name: DB_User
value: root
- name: DB_Password
value: dbpassword
To change it, instead of value
we would need to use valueFrom:
and secretKeyRef
as following
containers:
- env:
- name: DB_Host
valueFrom:
secretKeyRef:
name: db-secret
key: DB_Host
- name: DB_User
valueFrom:
secretKeyRef:
name: db-secret
key: DB_User
- name: DB_Password
valueFrom:
secretKeyRef:
name: db-secret
key: DB_Password
Now if we apply these changes we will see how our deployment starts using the new references instead of the hardcoded values, making our deployment much cleaner and safer.
controlplane $ k describe deploy
Name: webapp-deployment
Namespace: default
CreationTimestamp: Mon, 24 Jun 2024 09:03:46 +0000
Labels: <none>
Annotations: deployment.kubernetes.io/revision: 2
Selector: app=webapp
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=webapp
Containers:
webapp-container:
Image: nginx:latest
Port: 80/TCP
Host Port: 0/TCP
Environment:
DB_Host: <set to the key 'DB_Host' in secret 'db-secret'> Optional: false
DB_User: <set to the key 'DB_User' in secret 'db-secret'> Optional: false
DB_Password: <set to the key 'DB_Password' in secret 'db-secret'> Optional: false
Mounts: <none>
Volumes: <none>
Node-Selectors: <none>
Tolerations: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: webapp-deployment-b4ddb5755 (0/0 replicas created)
NewReplicaSet: webapp-deployment-6ff644ffb8 (1/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 23m deployment-controller Scaled up replica set webapp-deployment-b4ddb5755 to 1
Normal ScalingReplicaSet 89s deployment-controller Scaled up replica set webapp-deployment-6ff644ffb8 to 1
Normal ScalingReplicaSet 87s deployment-controller Scaled down replica set webapp-deployment-b4ddb5755 to 0 from 1
If you want to know more about Kubernetes, check all our posts in Kubernetes