Using Dockerfile, it has always been difficult to access private resources. There was simply no good solution. Using environment variables or simply deleting secret files after use is no good: they remain in the image metadata. Users sometimes went to tricks: they created multi-stage assemblies, but they still had to take extreme care so that at the final stage there were no secret values, and the secret files were kept in the local assembly cache before the cut.
The Docker build team 18.09 includes many updates. The main feature is that there is a completely new version of the implementation of the server part, it is offered as part of the Moby BuildKit project. The server application BuildKit has got new features, among which is the support of Dockerfile build secrets.
First of all, you need to enable the server part of BuildKit. BuildKit in version 18.09
is a selection function that can be enabled using the environment variable DOCKER_BUILDKIT=1
before running docker build
. In the next version, it is planned to make BuildKit the default server part.
export DOCKER_BUILDKIT=1
The implementation of build secrets is based on two new features of BuildKit. One of them is the ability to use the user interface loaded from images in the registry; the second is the possibility of using installation points in RUN
commands for Dockerfile. To use the implementation function with support for secrets (instead of the standard one), define the image of the linker using the syntax directive in the first line of the Dockerfile — pointing to the image of the container you want to use. So far, the secrets in the stable channel of external Dockerfile are not available: you need one of the versions in the experimental channel, for example, docker/dockerfile:experimental
or docker/dockerfile/1.0.0-experimental
.
# syntax=docker/dockerfile:1.0.0-experimental
If, as the author of the Dockerfile, you know that the RUN
command set in the Dockerfile requires a secret value, use the --mount
label for it, indicating what secret the command needs and where it should be mounted. The --mount
label accepts a comma-delimited structure as in --mount
for docker run
.
# syntax=docker/dockerfile:1.0.0-experimental FROM alpine RUN --mount=type=secret,id=mysite.key command-to-run
This label indicates that during operation the command has access to the secret file via the path /run/secrets/mysite.key
. The secret is available only to the team labeled mount, and not to other parts of the assembly. The data in this file is downloaded from the secret store based on the specified identifier "mysite.key". Currently, the Docker command-line interface supports the disclosure of secrets from local client files using the --secret
tag.
docker build --secret id=mysite.key,src=path/to/mysite.key .
As described above, the default secrets are set to /run/secrets
, however, you can specify any path using the “target” key. If “target” is specified, and “id” is not, then “id” defaults to the base name of the destination path.
Not necessarily limited to one secret. You can use any number of them, indicating different identifiers.
If the author of Dockerfile indicates that the RUN
instruction can use the secret, and the user invoking the assembly does not provide it, then the secret is ignored and no file is installed at the specified path. If this situation is undesirable, use the “required” key: this will show that the assembly will fail without a value.
# syntax=docker/dockerfile:1.0.0-experimental FROM alpine RUN --mount=type=secret,id=mysite.key,required <command-to-run>
The secret file is automatically installed only in a separate tmpfs file system in order to prevent leaks to the final image or the next command and that it is not stored in the local build cache.
Secret values ​​are also excluded from the build cache calculations so that the metadata cache cannot be used.
Most often, they are probably trying to gain access to private storages - through the SSH protocol. Yes, to unlock the SSH private key for the assembly, you can use secret elements, but there is a better solution. The SSH protocol uses public key cryptography, and thanks to this design, it is not necessary for anyone to communicate their private key. For example, if you use several computers with SSH, you do not need to transfer your key - it is enough to provide a connection through the ssh-A
protocol.
We added a similar feature to the docker build
, where you can use the --ssh
label to direct an existing SSH agent connection or key to the linker program. Instead of passing key information, Docker will simply notify the linker of the availability of this feature. If the linker needs access to the remote server via SSH, he will contact the client and ask for confirmation of the specific request necessary for the connection. The key itself does not leave the client program, and after the program that requires access, the linker does not have any data left to re-establish the remote connection.
Access to the file transfer via the SSH protocol is obtained only by commands in the Dockerfile that directly requested access to SSH by specifying the type=ssh
block. Other commands do not have data about an available SSH agent.
Another aspect of SSH is also worth noting - the use of the TOFU security model. When connecting to an SSH server for the first time, it will request information about an unknown host, since it does not have a public key available at the local level for this server and, accordingly, cannot verify whether the public key provided by the remote party is valid for this address.
When building with Dockerfile, the correctness of this request is not checked, and accordingly, the server's public key must already exist in the container trying to use SSH. There are several ways to get this public key. For example, it will be provided by the base image, or you copy it from the build context. If you want a simpler solution, run the ssh–keyscan
program as part of the build - it will load the current public key of the host.
To request SSH access to the RUN
command in the Dockerfile, you must specify a block with the “ssh” type. Then during the execution of the process, a socket will be installed with access to the SSH agent read-only. This will also set up the SSH_AUTH_SOCK
environment variable so that programs using the SSH protocol automatically use this socket.
# syntax=docker/dockerfile:experimental FROM alpine # install ssh client and git RUN apk add --no-cache openssh-client git # download public key for github.com RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts # clone our private repository RUN --mount=type=ssh git clone git@github.com:myorg/myproject.git myproject
On the client side of the Docker, you need to specify with the help of the --ssh
label that sending via SSH is allowed for this assembly.
docker build --ssh default .
A label accepts a pair of key values ​​defining the location of the local SSH agent socket or private keys. If you want to use the default=$SSH_AUTH_SOCK
, the path to the socket can be left blank.
In the Dockerfile block, you can also use the “id” key to separate different servers that are included in one assembly. For example, access to various repositories in the Dockerfile can be obtained with different deployment keys. In this case, in the Dockerfile you will use:
… RUN --mount=type=ssh,id=projecta git clone projecta … RUN --mount=type=ssh,id=projectb git clone projectb …
and reveal client data with docker build --ssh projecta=./projecta.pem --ssh projectb=./projectb.pem
. Notice that even if you specify the actual keys, only the agent connection is reported to the linker, and not the actual contents of these private keys.
This completes the review of the new features of assembly secrets in Docker 18.09. Hopefully, the new features will help to make more use of the Dockerfile capabilities in projects and ensure a higher level of security of the assembly line.
Source: https://habr.com/ru/post/432682/
All Articles