Question
How to use docker compose secrets with a non-root user when a file is required
Situation
The current (07/2024) docker compose documentation states (falsely) that there is a long-syntax when using 'docker secrets' that can defines the name, uid, gid and mode of the mounted file
services:
frontend:
image: example/webapp
secrets:
- source: server-certificate
target: server.cert
uid: "103"
gid: "103"
mode: 0440
secrets:
server-certificate:
file: ./server.cert
This configuration is valid but has no affect at all when using compose
(works for docker swarm).
There was an discussion (GitHub Docker Issue 9648) going on about it (showing the different implementations of this specification) but the documentation has not been fixed (GitHub Docker Issue 18907).
Result is a mounted secret (/run/secrets/<foobar>
) with root:root
(0:0
) ownership and permission mode 400
.
Sidenote: /run/secrets/
is a read-only mounted filesystem.
Problem
When using docker compose secrets on an image which does come with a non-root user shipped.
services:
nginx:
image: nginxinc/nginx-unprivileged:1.27-alpine
ports:
- "8080:8080"
secrets:
- FOO_BAR_SECRET
secrets:
FOO_BAR_SECRET:
file: .foo.bar
Solution idea (question)
Is there another/better solution then creating a custom image (Dockerfile) which switches back to the 'root' user and defines a wrapping docker entrypoint script
FROM nginxinc/nginx-unprivileged:1.27-alpine
## switching to non-root user 'nginx' later in custom 'docker-entrypoint-wrapper.sh'
USER root
## one could argue to just use pre-installed 'runuser' instead, or install 'gosu'. For this example there is no strong argument for either
RUN apk update && apk add su-exec
## enables us to run commands on startup as 'root'
RUN mv /docker-entrypoint.sh /docker-entrypoint-original.sh
COPY docker-entrypoint-wrapper.sh /docker-entrypoint.sh
RUN chmod ug+x /docker-entrypoint.sh
that copies those root-exclusive secrets from the read-only filesystem mount elsewhere, updates the file owner to the desired non-root user and switches back to this user to execute the actual/original docker entrypoint script ?
#!/usr/bin/env sh
set -e
mkdir /run/secrets_ \
&& cp -r /run/secrets/* /run/secrets_ \
&& chown -R nginx:nginx /run/secrets_
# as mentioned in the Docker file: may use 'gosu' or 'runuser' instead
exec su-exec nginx /docker-entrypoint-original.sh "${@}"
Solution alternative (question) when a file is not required or just not an option
Qudos to dcendents (GitHub) pointing out this idea (on GitHub Keycloak Issue 10816, that one could 'export' the content of the secret file. Which indeed is not really desired security-wise hence you may not export it but just make it 'inline available' for the command.
#!/bin/sh
## find all secret files mounted by docker
for i in $(ls -1 /run/secrets)
do
## export secret file name as environment variable
export "${i}"="$(cat /run/secrets/${i})"
done
## run actual command
exec "$@"
Research
There is one idea of having just an "simple" docker mount declaration with the desired ownership and permission mode but in that case one would be required to do it for every X different services and Y different secrets, which would lead to an "small" bloat of long repeating lines.
Restrictions/requirements on a solution
(A) I need or would like to have a general solution thats works for a Windows and Linux host. Scripting a chown on a Windows host may not be a way.
(B) The FOO_BAR_SECRET
will be used by multiple services (it will be a wildcard TLS certificate) which all requires different UIDs.