-
-
Notifications
You must be signed in to change notification settings - Fork 321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Makes docker images support non-root execution #1031
Conversation
Defaults to running as grist:grist if executed as root. Allows GRIST_DOCKER_USER and GRIST_DOCKER_GROUP to be passed to override the default de-escalation behaviour.
Hello, Nice work ! I built the image locally and did some tests. It seems that the chown on /grist is very very slow. Do you have the same behaviour ? |
sandbox/docker_entrypoint.sh
Outdated
|
||
for dir in "${important_dirs[@]}"; do | ||
# Make sure the target user owns everything that Grist needs read/write access to. | ||
find "$dir" ! -user "$target_user" -exec chown "$target_user" "{}" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not using chown -R "$target_user" "$dir"
instead? For performance reasons?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this line is derived from the postgres docker image:
https://github.com/docker-library/postgres/blame/29d4a29d8f750c7c55a22199311520de1e1f7f1e/docker-entrypoint.sh#L51
Which was changed from chown
as a time save / performance boost in this PR:
docker-library/postgres#463
Thanks @rouja! |
@Spoffy I just check what I did on a testing deployment on openshift. Actually I do not touch to /grist permissions and I put a volume only on /persist. So I think if /grist is read-only It's Ok. But maybe we can do the chown on /grist during the build to avoid that files to be owned by root user no matter the final running user ? |
I think the issue comes from the fact that /grist is quite big :
And when we do the chown we do it on the "docker image layer". For instance if we do something like that :
The start is much faster but I'm pretty sure that it's not the right way to fix our problem. |
Could you give it a try now @rouja? I moved the permissions changes for It does a mean user can't mount We also gain a little bit of security, as a compromised services wouldn't be able to modify the grist code to serve up malicious content. |
Hi, Looks good to me. @paulfitz what is the process to add a note in the release note ? Even if changes should be backwards-compatible, it's better to be safe than sorry. |
# 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why this isn't just a separate ENV
stanza?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Figured we may as well not create an extra docker layer for it :)
sandbox/docker_entrypoint.sh
Outdated
target_user=${GRIST_DOCKER_USER:-grist} | ||
target_group=${GRIST_DOCKER_GROUP:-grist} | ||
|
||
for dir in "${important_write_dirs[@]}"; do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loops over a one-element, hardcoded array.
What other write dirs are you foreseeing we'll have one day?
In my opinion, until the day comes when we have those directories, we just get rid of this loop and convert import_write_dirs
into a scalar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally, it had /grist
in there, which was removed when I realised it was better read-only for performance reasons (chown
takes ages).
To be honest - I'm not sure what we'd gain from converting it into a scalar right now? It'd be additional effort now, and more to undo it later if we did end up deciding it was needed.
Future examples may include:
- Splitting
/persist
into/data
,/config
or similar. - Fixing any issues if there ends up being a subdirectory of
/grist
that does need to be writable.
find "$dir" ! -user "$target_user" -exec chown "$target_user" "{}" + | ||
done | ||
|
||
# Restart as the target user, replacing the current process (replacement is needed for security). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What security does exec
give us? If we had a parent process, a security hole in setpriv
would keep the root process running, and thus potentially exploitable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly - exec throws away the parent process running as root.
Digging out the issue, it looks like it was a vuln mentioned in Gosu (an alternative to setpriv), concerning TTYs persisting across privilege boundaries:
tianon/gosu#37
The exec
gets rid of the parent shell, which removes vulnerabilities where malicious code can interact with it.
sandbox/docker_entrypoint.sh
Outdated
|
||
# Restart as the target user, replacing the current process (replacement is needed for security). | ||
# Alternative tools to setpriv are: chroot, gosu. | ||
exec setpriv --reuid "$target_user" --regid "$target_group" --init-groups /usr/bin/env bash "$BASH_SOURCE" "$@" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although a valid bashism, I find $BASH_SOURCE
a little obscure here, as you're relying on bash's behaviour of $lol
being a synonym for ${lol[0]}
for an array lol
.
Can we just call it $0
instead, as we do with other shell scripts in Grist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't remember why I chose one over the other - happy to move to $0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kicked the tires a bit and it seems to work as expected. Hard to imagine all the permutations of setups out there but this seems OK to me. Thanks @Spoffy !
Dockerfile
Outdated
|
||
# Add a user to allow de-escalating from root on startup | ||
RUN useradd -ms /bin/bash grist | ||
ENV GRIST_DOCKER_USER=grist\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny lint: maybe a space before the \ just so separation is immediately clear without needing to mentally process next line
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.
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:
/persist
is writable by that user./grist
remains owned by root and is read-only.Tested locally in these scenarios:
Addresses: