34

I want to pass a secret value needed by an app that runs in a Docker container. This particular container is short-lived -- it starts up, runs a command, and then terminates.

Method 1: Pass the value as an environment variable via the command line when starting the container (Docker supports this as a command line argument to starting a container). I feel like this is bad since the command will show up in process lists (with the key and all) on the host machine that started the docker container.

Method 2: Pass the value as an env variable via an env variable file. This solves the process list issue, but running docker info on the running container from the host shows a list of all the environment variables set for that container. This makes me believe Docker is storing these somewhere on disk on the host that is unsafe (since adding a new environment variable in the running container does not update this list, it must not be reading it directly in real-time).

In general, I feel like environment variables are not adequate to securely store secret data (even if only temporarily), but I do not have enough knowledge to back up this thought.

What is a secure method for passing secret data to a container?

  • Which operating system is this for? – LateralFractal Oct 16 '14 at 2:28
  • is the secret data consistent or different for each container? what exactly is the secret used for? – theterribletrivium Oct 16 '14 at 9:32
  • @LateralFractal CentOS 7 – Anthony Kraft Oct 16 '14 at 20:17
  • @theterribletrivium the secret data is consistent in this exact case (but some may not be). It is used to pass in a password for a first time initialization of a DB user. – Anthony Kraft Oct 16 '14 at 20:18
10

Environment variables are the best way to do this, specifically method 2. Docker, by default, does not allow itself to be run by users other than root. Access to the socket is prohibited. I'd say method 2 is reasonably safe, as out of the box if an attacker has root access (and can poke around in your docker containers) you're already in bad shape.

Two Docker security notes in general. Be super cautious with enabling the API, as by default there is no encryption or authentication. They have a way to use certs and TLS that they documented, but proceed with caution.

Also, if possible enable SELinux on your server. Newer versions of it are able to actually see docker containers and automatically build security contexts for each one. This prevents a container compromise from easily moving back into the host. By default docker runs as the root user, and even with the USER directive it still interfaces directly with the kernel unlike a VM. This exposes the host to any local privilege exploit as soon as a docker container is compromised.

  • You seem knowledgeable on this topic, so I'll ask: SELinux with containers works best when you can assign an MCS category to the running container, and label the files it needs with that MCS label to prevent other different containers from accessing other containers' files (in the event of a break out). I cannot seem to find a way to set the MCS label on a docker container (it can only be randomly generated), which makes it difficult to deal with when I update the container (i.e. new random label that can't access the files). Do you know if there is a way to handle this? – Anthony Kraft Oct 16 '14 at 20:41
  • 1
    Also, how does this change for more persistent info, like say, an ssl private key? – Anthony Kraft Oct 16 '14 at 21:00
  • Can you go into a little more detail in regards to what files you're accessing? I wouldn't typically expect or want one container to talk to another in general, except in cases involving performance or that sort of thing. To the SSL key, it doesn't change it much. Just guard the material the same way you would otherwise. – theterribletrivium Oct 16 '14 at 22:41
  • The most common scenario I can think of is mounting a DB data volume from the host. If you put out a new db image, I would like to be able to specify the MCS category instead of getting a random one. Interesting, Docker 1.3 was announced a few hours ago and appears to have solved this with the new --security-opt command line arg to the run command – Anthony Kraft Oct 17 '14 at 0:39
  • 1
    @theterribletrivium using environment variables for secrets is officially considered a hack: Features / hacks that are (mis)used for secrets – Sled Jan 8 '16 at 21:54
6

Docker guys have recently introduced their native solution for this: https://blog.docker.com/2017/02/docker-secrets-management/

The usage pattern is:

$ echo "This is a secret" | docker secret create my_secret_data -
$ docker service  create --name="redis" --secret="my_secret_data" redis:alpine

The unencrypted secret is then mounted into the container in an in-memory filesystem at /run/secrets/<secret_name>.

Though this is only accessible within a swarm

You can find full documentation here: https://docs.docker.com/engine/swarm/secrets/

  • This is a great update, but please include the relevant parts of the link in your answer. – schroeder Apr 27 '17 at 11:48
1

docker secret only works in swarm mode. For local mode, to pass some simple secret, such as password, we can read the password into a variable from stdin. The difficulty comes with the detach mode, which will hang while reading the pipe within the container. Here is a trick to work around:

cid=$(docker run -d -i alpine sh -c 'read A; echo "[$A]"; exec some-server')
docker exec -i $cid sh -c 'cat > /proc/1/fd/0' <<< _a_secret_

First, create the docker daemon with -i option, the command read A will hang waiting for the input from /proc/1/fd/0; Then run the second docker command, reading the secret from stdin and redirect to the last hanging process. The secret will only be read once, and will not be inspected.

1

Short answer

docker build has --secret option for API version 1.39+.

Long answer

API version 1.39+ means docker 18.09.0+

In release notes, under "New features for Docker Engine EE and CE" section at 18.09.0 says:

  • Updated API version to 1.39 moby/moby#37640

"Build Enhancements for Docker" page in guides has a bit outdated explanation.

I found --secret option at New Docker Build secret information, but the explanation here turned out to be outdated. It says

This Dockerfile is only to demonstrate that the secret can be accessed. As you can see the secret printed in the build output. The final image built will not have the secret file

but actually the secret is not printed in the build output. I think it is guarded for security.

"Dockerfile frontend experimental syntaxes" page in buildkit has up-to-date explanation.

Then I found the following page.

How to use docker build --secret

Here is the steps to follow.

  1. Make sure you use the required version of docker.
$ docker version
Client: Docker Engine - Community
 Version:           19.03.2
 API version:       1.40
 Go version:        go1.12.8
 Git commit:        6a30dfc
 Built:             Thu Aug 29 05:29:11 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.2
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.8
  Git commit:       6a30dfc
  Built:            Thu Aug 29 05:27:45 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc8
  GitCommit:        425e105d5a03fabd737a126ad93d62a9eeede87f
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
  1. Set DOCKER_BUILDKIT environment variable to 1
$ export DOCKER_BUILDKIT=1
  1. Create a secret file.
$ echo "It's a secret" > mysecret.txt
  1. Create a Dockerfile.
$ cat <<EOF > Dockerfile
# syntax = docker/dockerfile:experimental
FROM alpine
RUN --mount=type=secret,id=mysecret,target=/foobar cat /foobar | tee /output
EOF

Make sure you have # syntax = docker/dockerfile:experimental at the first line in Dockerfile. Note the above example is just for demo. You should not save the content of secret in actual usage.

  1. Run docker build with --secret option.
$ docker build -t secret-example --secret id=mysecret,src=mysecret.txt .
[+] Building 2.3s (8/8) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 176B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => resolve image config for docker.io/docker/dockerfile:experimental
 => CACHED docker-image://docker.io/docker/dockerfile:experimental@sha256:888f21826273409b5ef5ff9ceb90c64a8f8ec7760da30d1ffbe6c3e2d323a7bd
 => [internal] load metadata for docker.io/library/alpine:latest
 => CACHED [1/2] FROM docker.io/library/alpine
 => [2/2] RUN --mount=type=secret,id=mysecret,target=/foobar cat /foobar | tee /output
 => exporting to image
 => => exporting layers
 => => writing image sha256:22c44473107b6d1f92095c6400613a7e82c9835f5baaa85853a114e4bb5d8744
 => => naming to docker.io/library/secret-example

Note the content of mysecret.txt is NOT printed even in the build output.

Verify the secret is correctly used. Again this is just for demo purpose.

$ docker run -t secret-example cat /output
It's a secret

I noticed the content of /foobar is not saved, but empty file remains in the built image.

$ docker run -t secret-example ls -l /foobar
-rwxr-xr-x    1 root     root             0 Sep 16 19:16 /foobar
New contributor
hnakamur is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.