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 -- "$@"