From a8431c69a735bf54df80a81134ca059bd8f29c5e Mon Sep 17 00:00:00 2001 From: Spoffy <4805393+Spoffy@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:24:32 +0100 Subject: [PATCH] Makes docker images default to non-root execution (#1031) De-escalates to a normal user when the docker image is run as root. Allows GRIST_DOCKER_USER and GRIST_DOCKER_GROUP to be passed to override the default de-escalation behaviour. Backwards compatible with previous root installations. -------- This change adds a new docker_entrypoint.sh, which when run as root de-escalates to the provided user, defaulting to grist:grist. This is similar to the approach used by the official postgres docker image. To achieve backwards compatibility, it changes ownership of any files in `/persist` to the user it's given at runtime. Since the docker container is typically run as root, this should always work. If the container is run as a standard user from the very start: * It's the admin's responsibility to ensure `/persist` is writable by that user. * `/grist` remains owned by root and is read-only. --- Dockerfile | 11 +++++++++- sandbox/docker_entrypoint.sh | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100755 sandbox/docker_entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 35148cdb..cdd584f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -122,6 +122,15 @@ RUN \ mv /grist/static-built/* /grist/static && \ rmdir /grist/static-built +# To ensure non-root users can run grist, 'other' users need read access (and execute on directories) +# This should be the case by default when copying files in. +# Only uncomment this if running into permissions issues, as it takes a long time to execute on some systems. +# RUN chmod -R o+rX /grist + +# Add a user to allow de-escalating from root on startup +RUN useradd -ms /bin/bash grist +ENV GRIST_DOCKER_USER=grist \ + GRIST_DOCKER_GROUP=grist WORKDIR /grist # Set some default environment variables to give a setup that works out of the box when @@ -151,5 +160,5 @@ ENV \ EXPOSE 8484 -ENTRYPOINT ["/usr/bin/tini", "-s", "--"] +ENTRYPOINT ["./sandbox/docker_entrypoint.sh"] CMD ["node", "./sandbox/supervisor.mjs"] diff --git a/sandbox/docker_entrypoint.sh b/sandbox/docker_entrypoint.sh new file mode 100755 index 00000000..7072e07e --- /dev/null +++ b/sandbox/docker_entrypoint.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Runs the command provided as arguments, but attempts to configure permissions first. + +important_read_dirs=("/grist" "/persist") +write_dir="/persist" +current_user_id=$(id -u) + +# We want to avoid running Grist as root if possible. +# Try to setup permissions and de-elevate to a normal user. +if [[ $current_user_id == 0 ]]; then + target_user=${GRIST_DOCKER_USER:-grist} + target_group=${GRIST_DOCKER_GROUP:-grist} + + # Make sure the target user owns everything that Grist needs write access to. + find $write_dir ! -user "$target_user" -exec chown "$target_user" "{}" + + + # Restart as the target user, replacing the current process (replacement is needed for security). + # Alternative tools to setpriv are: chroot, gosu. + # Need to use `exec` to close the parent shell, to avoid vulnerabilities: https://github.com/tianon/gosu/issues/37 + exec setpriv --reuid "$target_user" --regid "$target_group" --init-groups /usr/bin/env bash "$0" "$@" +fi + +# Validate that this user has access to the top level of each important directory. +# There might be a benefit to testing individual files, but this is simpler as the dir may start empty. +for dir in "${important_read_dirs[@]}"; do + if ! { test -r "$dir" ;} ; then + echo "Invalid permissions, cannot read '$dir'. Aborting." >&2 + exit 1 + fi +done +for dir in "${important_write_dirs[@]}"; do + if ! { test -r "$dir" && test -w "$dir" ;} ; then + echo "Invalid permissions, cannot write '$dir'. Aborting." >&2 + exit 1 + fi +done + +exec /usr/bin/tini -s -- "$@"