Secrets of building and sending SSH to Docker 18.09

Original author: Tõnis Tiigi
  • Transfer
image

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 the tricks: they created multistage 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 server-side implementation, 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.


Use of secrets


First of all, you need to enable the server part of BuildKit. BuildKit in a version 18.09is a selection function that can be enabled using the environment variable DOCKER_BUILDKIT=1before launch 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 commands RUNfor the 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, 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:experimentalor docker/dockerfile/1.0.0-experimental.


# syntax=docker/dockerfile:1.0.0-experimental

If you, as the author of Dockerfile, know that the command RUNset in the Dockerfile requires a secret value, use the label for it --mount, indicating what secret the command needs and where it should be mounted. The label --mountaccepts a comma-separated structure as in --mountfor 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 along the way /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 a label --secret.


docker build --secret id=mysite.key,src=path/to/mysite.key .

As described above, the default secrets are set to /run/secrets, but you can specify any path using the key "target". 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 instruction RUNcan use the secret, and the user who calls 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>

Implementation


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.


Ssh


Most often, they are probably trying to get 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 protocol ssh-A.


We added a similar feature in docker buildwhere you can use a label --sshto 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, it 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 a block type=ssh. 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 side 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 provide a basic image, or you copy it from the context of the assembly. If you want a simpler solution, run the program as part of the build ssh–keyscan- it will load the current public key of the host.


To request SSH access to a command RUNin 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. It will also set up an environment variable SSH_AUTH_SOCKso 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

From the Docker client side, you need to --sshspecify with the help of a label that SSH transfer is allowed for this assembly.


docker build --ssh default .

The label accepts a pair of key values ​​that determine the location of the local SSH agent socket or private keys. If you want to use a value default=$SSH_AUTH_SOCK, you can leave the path to the socket empty.


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.


Also popular now: