Understanding secrets in Kubernetes
Highlights some essential Kubernetes knowledges that will make us comfortable when dealing with secret objects for our applications workloads.
Table of contents
Overview
- Kubernetes secret object can be used to store sensitive informations
- The sensitive informations could then be securely transferred to pods or directly used by other Kubernetes resources
- Sensitive informations inside secret object are by default base64 encoded
- For other infos and cautions see Kubernetes Secrets
Secret types
Asking for help on secret object creation shows 3 types of secrets :
$ kubectl create secret -h
Create a secret using specified subcommand.
Available Commands:
docker-registry Create a secret for use with a Docker registry
generic Create a secret from a local file, directory, or literal
value
tls Create a TLS secret
- We use the
docker-registry
secret type to create secrets for authenticating with container registries from different providers, not only Docker. A typical use case of this type of secret is pulling/pushing private container images from/to container registries. To create a secret of that type, you need :- the address of the container registry
- the name of the authenticating user
- the associated password for the authenticating user
- The
generic
secret type will be used to storekey:value
pair data. The data could then be retrieved inside pod(s) as files or environment variables - The
tls
secret type will be used to store TLS certificates and keys. The certificate and associated private key data must be.PEM
encoded. Could be used for instance with ingress controllers to make our apps accessible in HTTPS
Creating secrets
Docker-registry
Here is the creation command :
$ kubectl create secret docker-registry my-docker-registry-secret --docker-server=my-registry.example.com --docker-username=myuser --docker-password=mypassword
Let's have a look at the created secret :
$ kubectl get secret my-docker-registry-secret
NAME TYPE DATA AGE
my-docker-registry-secret kubernetes.io/dockerconfigjson 1 12s
$ kubectl get secret my-docker-registry-secret -o yaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJteS1yZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6Im15dXNlciIsInBhc3N3b3JkIjoibXlwYXNzd29yZCIsImF1dGgiOiJiWGwxYzJWeU9tMTVjR0Z6YzNkdmNtUT0ifX19
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-docker-registry-secret
namespace: juiceshop
resourceVersion: "546844"
uid: fd824b18-aacb-4341-a056-c3beb24b1162
type: kubernetes.io/dockerconfigjson
Let's see the base64 decoded value of the .dockerconfigjson
data key :
$ echo "eyJhdXRocyI6eyJteS1yZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6Im15dXNlciIsInBhc3N3b3JkIjoibXlwYXNzd29yZCIsImF1dGgiOiJiWGwxYzJWeU9tMTVjR0Z6YzNkdmNtUT0ifX19" | base64 -d | jq
{
"auths": {
"my-registry.example.com": {
"username": "myuser",
"password": "mypassword",
"auth": "bXl1c2VyOm15cGFzc3dvcmQ="
}
}
}
The Google Container Registry case
For the Google Container Registry, we can't generate a couple username/password for authentication. Instead, we have to create a Google service account and give that service account the permission to read data from GCS (Google Cloud Storage).
We can then use that service account token to create the docker-registry
secret. Assuming that the ~/gcr-image-pull-token.json
file contains the service account token, we will use the following values for the registry username and password within the docker-registry
secret creation command :
--docker-username=_json_key
--docker-password="$(cat ~/gcr-image-pull-token.json)"
TLS
Here is the creation command :
kubectl create secret tls my-tls-secret --key=tls-key.pem --cert=tls-cert.pem
Here is a truncated output of the content of the TLS certificate and private key files we used :
tls-cert.pem
-----BEGIN CERTIFICATE-----
MIIDzDCCArSgAwIBAgIUOiPOFvdtyD/wOCrgaoGNsV6CuIEwDQYJKoZIhvcNAQEL
BQAwYTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
(...)
jJBYxC4Hi6ZNnb2/UUliumVmmQZvLLcyNK+rcbGGPtP0wB+QoXhDUrKAsrM/McTK
f1IAy/XYLdk9UUcLxpN2Iw==
-----END CERTIFICATE-----
tls-key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2OmXD24qOvorrGqB3GOdeNdHF1v6QIb3CSA4tNfqH3fqQ22F
iEsXHt8s2dfHlSqwZ4J5bwwzjp9eXqwwNRPDwWIaFzesWh1qsWxJIvh9Z2b/9TUD
(...)
5C9jvJzMZRAqQ2dWd0yO00a6kf/Uyi/dzVpx96iwf0nwj0R0dEndudHkH0awBAwi
JZvsLB7gYCceCXs+S/OROtFh0322pBTHwmBTSyUARRF2VOfC3bQj
-----END RSA PRIVATE KEY-----
Now let's have a look at the created secret :
$ kubectl get secret my-tls-secret
NAME TYPE DATA AGE
my-tls-secret kubernetes.io/tls 2 12m
$ kubectl get secret my-tls-secret -o yaml
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR6RENDQXJTZ0F3SUJBZ0lVT2lQT0Z2ZHR5RC93T0
(...truncated)
hoRFVyS0Fzck0vTWNUSwpmMUlBeS9YWUxkazlVVWNMeHBOMkl3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMk9tWEQyNHFPdm9yck
(...truncated)
Z1lDY2VDWHMrUy9PUk90RmgwMzIycEJUSHdtQlRTeVVBUlJGMlZPZkMzYlFqCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-tls-secret
namespace: juiceshop
resourceVersion: "651996"
uid: b5913af1-5fab-45b2-be2d-252b7a829ea7
type: kubernetes.io/tls
Generic
The kubectl create secret generic -h
command shows examples for creating generic secrets. Let's try some of them and see the result.
- Create a new secret named
my-secret
withkey1=supersecret
andkey2=topsecret
$ kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
key1: c3VwZXJzZWNyZXQ=
key2: dG9wc2VjcmV0
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "655183"
uid: 4bec3a1f-f7b9-4d5f-bd5e-f218e94a218f
type: Opaque
- Create a new secret named
my-secret
using a combination of a file and a literal
$ cat bar/db_password
superpass
$ kubectl create secret generic my-secret --from-file=bar/db_password --from-literal=passphrase=topsecret
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
db_password: c3VwZXJwYXNzCg==
passphrase: dG9wc2VjcmV0
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "655590"
uid: 5fe8546b-489c-4986-a711-69a4155e7f11
type: Opaque
- Create a new secret named
my-secret
with specified key name. From the previous example, we will set the key name todatabase_password
instead of the defaultdb_password
taken from the filename
$ cat bar/db_password
superpass
$ kubectl create secret generic my-secret --from file=database_password=bar/db_password
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
database_password: c3VwZXJwYXNzCg==
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "655654"
uid: 362c9267-7e35-4101-a659-ae7e56c0e420
type: Opaque
- Create a new secret named
my-secret
from a file containing a bunch of lines withkey=value
pairs
$ cat app.env
db_user=user
db_password=superpass
$ kubectl create secret generic my-secret --from-env-file=app.env
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
db_password: c3VwZXJwYXNz
db_user: dXNlcg==
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "653254"
uid: 0297bafd-fffa-4af2-adbf-97cd3147de1f
type: Opaque
- Create a new secret named
my-secret
with keys taken from each filename in folder bar and values from the associated files contents
$ ls bar/
db_password db_user
$ cat bar/db_user
user
$ cat bar/db_password
superpass
$ kubectl create secret generic my-secret --from-file=bar/
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
db_password: c3VwZXJwYXNzCg==
db_user: dXNlcgo=
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "654889"
uid: ea7d7829-240c-4cbe-a3ec-9bb6750377ea
type: Opaque
Using secrets
From pods as file
Here is how we make existing Kubernetes secrets available inside pods as file(s) :
- In our Kubernetes manifests used to create pods, we need to declare a
volume
inside podsspec
section, that will be supplied with one or many of our existing secrets - According to Kubernetes documentation, volumes containing secrets are backed by tmpfs (a RAM-backed filesystem) so they are never written to non-volatile storage
- We then have to declare a
volumeMounts
inside podsspec.containers
section, to mount the secret(s) as file(s) to specific path(s) inside the pod(s) container(s). Here is an example :
apiVersion: apps/v1
kind: Deployment
(...)
spec:
(...)
template:
(...)
spec:
containers:
- name: db
image: mariadb
env:
(...)
- name: MYSQL_PASSWORD_FILE
value: "/run/secrets/mysql/user_password"
- name: MYSQL_ROOT_PASSWORD_FILE
value: "/run/secrets/mysql/root_password"
volumeMounts:
(...)
- name: secret-volume
mountPath: /run/secrets/mysql
(...)
volumes:
(...)
- name: secret-volume
secret:
secretName: mysql-secret
- The data from the existing Kubernetes secret named
mysql-secret
will be made available to pods into the/run/secrets/mysql
directory - All data keys from the
mysql-secret
secret will be name of files inside the/run/secrets/mysql
directory and the associated key values the files contents
Here is an example. The following key/value pairs data from the mysql-secret
secret :
user_password: O3hndfn!dSZu
root_password: kHj483iYh?d2
will be available inside the pods containers as the following files :
/run/secrets/mysql/user_password
containningO3hndfn!dSZu
/run/secrets/mysql/root_password
containningkHj483iYh?d2
We could also specify a specific file path from where we want a specific secret data to be available. That path is relative to the mountPath
of the secret volume (/run/secrets/mysql
in this case).
Here is an example. If we wanted to make the user_password
secret available from /run/secrets/mysql/other_dir/other_user_password_filename
instead of /run/secrets/mysql/user_password
, we would have used :
(...)
volumeMounts:
- name: secret-volume
mountPath: /run/secrets/mysql
(...)
volumes:
- name: secret-volume
secret:
secretName: mysql-secret
items:
- key: user_password
path: other_dir/other_user_password_filename
From pods as environment variables
- The Kubernetes generic secret named
mysecret
exists and has been created with the--from-env-file
option of thekubectl create secret generic
command, from the following file content :
username=myusername
password=mypassword
- The following Kubernetes Deployment manifest will make
username
andpassword
data frommysecret
available to pod container(s) as environment variables :
apiVersion: apps/v1
kind: Deployment
(...)
spec:
(...)
template:
(...)
spec:
containers:
- name: my-container-name
image: my-container-image
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
- The created environment variables inside the pod container(s) are :
USERNAME=myusername
PASSWORD=mypassword
To pull private container images
Simply use the spec.imagePullSecrets
to specify the name of the docker-registry
secret to use for authentication when pulling private container images defined inside Kubernetes manifests files. Here is an example :
apiVersion: apps/v1
kind: Deployment
(...)
spec:
(...)
template:
(...)
spec:
imagePullSecrets:
- name: my-docker-registry-secret
containers:
- name: my-container-name
image: myregistry.example.com/my-private-container-image
(...)