Distribute Credentials Securely Using Secrets

This page shows how to securely inject sensitive data, such as passwords and encryption keys, into Pods.

Before you begin

You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. It is recommended to run this tutorial on a cluster with at least two nodes that are not acting as control plane hosts. If you do not already have a cluster, you can create one by using minikube or you can use one of these Kubernetes playgrounds:

Convert your secret data to a base-64 representation

Suppose you want to have two pieces of secret data: a username my-app and a password 39528$vdg7Jb. First, use a base64 encoding tool to convert your username and password to a base64 representation. Here’s an example using the commonly available base64 program:

  1. echo -n 'my-app' | base64
  2. echo -n '39528$vdg7Jb' | base64

The output shows that the base-64 representation of your username is bXktYXBw, and the base-64 representation of your password is Mzk1MjgkdmRnN0pi.

Caution: Use a local tool trusted by your OS to decrease the security risks of external tools.

Create a Secret

Here is a configuration file you can use to create a Secret that holds your username and password:

pods/inject/secret.yaml Distribute Credentials Securely Using Secrets - 图1

  1. apiVersion: v1
  2. kind: Secret
  3. metadata:
  4. name: test-secret
  5. data:
  6. username: bXktYXBw
  7. password: Mzk1MjgkdmRnN0pi
  1. Create the Secret

    1. kubectl apply -f https://k8s.io/examples/pods/inject/secret.yaml
  2. View information about the Secret:

    1. kubectl get secret test-secret

    Output:

    1. NAME TYPE DATA AGE
    2. test-secret Opaque 2 1m
  3. View more detailed information about the Secret:

    1. kubectl describe secret test-secret

    Output:

    1. Name: test-secret
    2. Namespace: default
    3. Labels: <none>
    4. Annotations: <none>
    5. Type: Opaque
    6. Data
    7. ====
    8. password: 13 bytes
    9. username: 7 bytes

Create a Secret directly with kubectl

If you want to skip the Base64 encoding step, you can create the same Secret using the kubectl create secret command. For example:

  1. kubectl create secret generic test-secret --from-literal='username=my-app' --from-literal='password=39528$vdg7Jb'

This is more convenient. The detailed approach shown earlier runs through each step explicitly to demonstrate what is happening.

Create a Pod that has access to the secret data through a Volume

Here is a configuration file you can use to create a Pod:

pods/inject/secret-pod.yaml Distribute Credentials Securely Using Secrets - 图2

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: secret-test-pod
  5. spec:
  6. containers:
  7. - name: test-container
  8. image: nginx
  9. volumeMounts:
  10. # name must match the volume name below
  11. - name: secret-volume
  12. mountPath: /etc/secret-volume
  13. readOnly: true
  14. # The secret data is exposed to Containers in the Pod through a Volume.
  15. volumes:
  16. - name: secret-volume
  17. secret:
  18. secretName: test-secret
  1. Create the Pod:

    1. kubectl apply -f https://k8s.io/examples/pods/inject/secret-pod.yaml
  2. Verify that your Pod is running:

    1. kubectl get pod secret-test-pod

    Output:

    1. NAME READY STATUS RESTARTS AGE
    2. secret-test-pod 1/1 Running 0 42m
  3. Get a shell into the Container that is running in your Pod:

    1. kubectl exec -i -t secret-test-pod -- /bin/bash
  4. The secret data is exposed to the Container through a Volume mounted under /etc/secret-volume.

    In your shell, list the files in the /etc/secret-volume directory:

    1. # Run this in the shell inside the container
    2. ls /etc/secret-volume

    The output shows two files, one for each piece of secret data:

    1. password username
  5. In your shell, display the contents of the username and password files:

    1. # Run this in the shell inside the container
    2. echo "$( cat /etc/secret-volume/username )"
    3. echo "$( cat /etc/secret-volume/password )"

    The output is your username and password:

    1. my-app
    2. 39528$vdg7Jb

Modify your image or command line so that the program looks for files in the mountPath directory. Each key in the Secret data map becomes a file name in this directory.

Project Secret keys to specific file paths

You can also control the paths within the volume where Secret keys are projected. Use the .spec.volumes[].secret.items field to change the target path of each key:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: mypod
  5. spec:
  6. containers:
  7. - name: mypod
  8. image: redis
  9. volumeMounts:
  10. - name: foo
  11. mountPath: "/etc/foo"
  12. readOnly: true
  13. volumes:
  14. - name: foo
  15. secret:
  16. secretName: mysecret
  17. items:
  18. - key: username
  19. path: my-group/my-username

When you deploy this Pod, the following happens:

  • The username key from mysecret is available to the container at the path /etc/foo/my-group/my-username instead of at /etc/foo/username.
  • The password key from that Secret object is not projected.

If you list keys explicitly using .spec.volumes[].secret.items, consider the following:

  • Only keys specified in items are projected.
  • To consume all keys from the Secret, all of them must be listed in the items field.
  • All listed keys must exist in the corresponding Secret. Otherwise, the volume is not created.

Set POSIX permissions for Secret keys

You can set the POSIX file access permission bits for a single Secret key. If you don’t specify any permissions, 0644 is used by default. You can also set a default POSIX file mode for the entire Secret volume, and you can override per key if needed.

For example, you can specify a default mode like this:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: mypod
  5. spec:
  6. containers:
  7. - name: mypod
  8. image: redis
  9. volumeMounts:
  10. - name: foo
  11. mountPath: "/etc/foo"
  12. volumes:
  13. - name: foo
  14. secret:
  15. secretName: mysecret
  16. defaultMode: 0400

The Secret is mounted on /etc/foo; all the files created by the secret volume mount have permission 0400.

Note: If you’re defining a Pod or a Pod template using JSON, beware that the JSON specification doesn’t support octal literals for numbers because JSON considers 0400 to be the decimal value 400. In JSON, use decimal values for the defaultMode instead. If you’re writing YAML, you can write the defaultMode in octal.

Define container environment variables using Secret data

You can consume the data in Secrets as environment variables in your containers.

If a container already consumes a Secret in an environment variable, a Secret update will not be seen by the container unless it is restarted. There are third party solutions for triggering restarts when secrets change.

Define a container environment variable with data from a single Secret

  • Define an environment variable as a key-value pair in a Secret:

    1. kubectl create secret generic backend-user --from-literal=backend-username='backend-admin'
  • Assign the backend-username value defined in the Secret to the SECRET_USERNAME environment variable in the Pod specification.

    pods/inject/pod-single-secret-env-variable.yaml Distribute Credentials Securely Using Secrets - 图3

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: env-single-secret
    5. spec:
    6. containers:
    7. - name: envars-test-container
    8. image: nginx
    9. env:
    10. - name: SECRET_USERNAME
    11. valueFrom:
    12. secretKeyRef:
    13. name: backend-user
    14. key: backend-username
  • Create the Pod:

    1. kubectl create -f https://k8s.io/examples/pods/inject/pod-single-secret-env-variable.yaml
  • In your shell, display the content of SECRET_USERNAME container environment variable.

    1. kubectl exec -i -t env-single-secret -- /bin/sh -c 'echo $SECRET_USERNAME'

    The output is similar to:

    1. backend-admin

Define container environment variables with data from multiple Secrets

  • As with the previous example, create the Secrets first.

    1. kubectl create secret generic backend-user --from-literal=backend-username='backend-admin'
    2. kubectl create secret generic db-user --from-literal=db-username='db-admin'
  • Define the environment variables in the Pod specification.

    pods/inject/pod-multiple-secret-env-variable.yaml Distribute Credentials Securely Using Secrets - 图4

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: envvars-multiple-secrets
    5. spec:
    6. containers:
    7. - name: envars-test-container
    8. image: nginx
    9. env:
    10. - name: BACKEND_USERNAME
    11. valueFrom:
    12. secretKeyRef:
    13. name: backend-user
    14. key: backend-username
    15. - name: DB_USERNAME
    16. valueFrom:
    17. secretKeyRef:
    18. name: db-user
    19. key: db-username
  • Create the Pod:

    1. kubectl create -f https://k8s.io/examples/pods/inject/pod-multiple-secret-env-variable.yaml
  • In your shell, display the container environment variables.

    1. kubectl exec -i -t envvars-multiple-secrets -- /bin/sh -c 'env | grep _USERNAME'

    The output is similar to:

    1. DB_USERNAME=db-admin
    2. BACKEND_USERNAME=backend-admin

Configure all key-value pairs in a Secret as container environment variables

Note: This functionality is available in Kubernetes v1.6 and later.

  • Create a Secret containing multiple key-value pairs

    1. kubectl create secret generic test-secret --from-literal=username='my-app' --from-literal=password='39528$vdg7Jb'
  • Use envFrom to define all of the Secret’s data as container environment variables. The key from the Secret becomes the environment variable name in the Pod.

    pods/inject/pod-secret-envFrom.yaml Distribute Credentials Securely Using Secrets - 图5

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: envfrom-secret
    5. spec:
    6. containers:
    7. - name: envars-test-container
    8. image: nginx
    9. envFrom:
    10. - secretRef:
    11. name: test-secret
  • Create the Pod:

    1. kubectl create -f https://k8s.io/examples/pods/inject/pod-secret-envFrom.yaml
  • In your shell, display username and password container environment variables.

    1. kubectl exec -i -t envfrom-secret -- /bin/sh -c 'echo "username: $username\npassword: $password\n"'

    The output is similar to:

    1. username: my-app
    2. password: 39528$vdg7Jb

Example: Provide prod/test credentials to Pods using Secrets

This example illustrates a Pod which consumes a secret containing production credentials and another Pod which consumes a secret with test environment credentials.

  1. Create a secret for prod environment credentials:

    1. kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11

    The output is similar to:

    1. secret "prod-db-secret" created
  2. Create a secret for test environment credentials.

    1. kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests

    The output is similar to:

    1. secret "test-db-secret" created

    Note:

    Special characters such as $, \, *, =, and ! will be interpreted by your shell) and require escaping.

    In most shells, the easiest way to escape the password is to surround it with single quotes ('). For example, if your actual password is S!B\*d$zDsb=, you should execute the command as follows:

    1. kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B\*d$zDsb='

    You do not need to escape special characters in passwords from files (--from-file).

  3. Create the Pod manifests:

    1. cat <<EOF > pod.yaml
    2. apiVersion: v1
    3. kind: List
    4. items:
    5. - kind: Pod
    6. apiVersion: v1
    7. metadata:
    8. name: prod-db-client-pod
    9. labels:
    10. name: prod-db-client
    11. spec:
    12. volumes:
    13. - name: secret-volume
    14. secret:
    15. secretName: prod-db-secret
    16. containers:
    17. - name: db-client-container
    18. image: myClientImage
    19. volumeMounts:
    20. - name: secret-volume
    21. readOnly: true
    22. mountPath: "/etc/secret-volume"
    23. - kind: Pod
    24. apiVersion: v1
    25. metadata:
    26. name: test-db-client-pod
    27. labels:
    28. name: test-db-client
    29. spec:
    30. volumes:
    31. - name: secret-volume
    32. secret:
    33. secretName: test-db-secret
    34. containers:
    35. - name: db-client-container
    36. image: myClientImage
    37. volumeMounts:
    38. - name: secret-volume
    39. readOnly: true
    40. mountPath: "/etc/secret-volume"
    41. EOF

    Note: How the specs for the two Pods differ only in one field; this facilitates creating Pods with different capabilities from a common Pod template.

  4. Apply all those objects on the API server by running:

    1. kubectl create -f pod.yaml

Both containers will have the following files present on their filesystems with the values for each container’s environment:

  1. /etc/secret-volume/username
  2. /etc/secret-volume/password

You could further simplify the base Pod specification by using two service accounts:

  1. prod-user with the prod-db-secret
  2. test-user with the test-db-secret

The Pod specification is shortened to:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: prod-db-client-pod
  5. labels:
  6. name: prod-db-client
  7. spec:
  8. serviceAccount: prod-db-client
  9. containers:
  10. - name: db-client-container
  11. image: myClientImage

References

What’s next