Passing secret keys securely to docker containers
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.
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/
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.
- buildkit/experimental.md at master ・ moby/buildkit
- buildkit/experimental.md at 1bf8190 ・ moby/buildkit as of writing this.
How to use docker build --secret
Here is the steps to follow.
- 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
- Set
DOCKER_BUILDKIT
environment variable to1
$ export DOCKER_BUILDKIT=1
- Create a secret file.
$ echo "It's a secret" > mysecret.txt
- 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.
- 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