-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Support non-root users (runtime UID remapping) #640
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
Conversation
Checking this non working code for the record. The idea is to usermod to change the uid:gid in entrypoint script. There are two approaches I took to do this: 1. pass the -u flag to docker run and try to do the usermod. Won't work because we're not in the passwd file aand so user mod fails 2. pass env vars with the host uid:gid you want to run as, and then usermod to change in-flight (fails, as we're trying to change the uid while logged in/running proc It seems eding the container /etc/passwd file is the only workable approach. One more thing to try is to run eentrypoint.sh before dumbinit - will do that next
I added fixuid to the image to support non root execution.
Dockerfile
Outdated
# We create first instead of just using WORKDIR as when WORKDIR creates, the user is root. | ||
RUN mkdir -p /home/coder/project | ||
RUN mkdir -p /home/coder/workdir |
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 renaming the directory necessary here? I don't see any problem, but there might be documentation elsewhere which might go out of sync due to this change.
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.
I think it's a good idea, especially if we want to restrict container FS writes to the coder user. We're making the work directory a sub-path of the $HOME of the coder directory, as all docker instructions will be invoked as the coder user at this point. It also means that we can volume mount the /home/coder/project directory without risking masking/overwriting the entrypoint.sh script.
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.
If you are creating a yet another directory, shouldn't one already existing be left alone as it is? If you are simply renaming it to something else in the same location then I am not sure what are you gaining from it, but I might be missing something.
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.
check my latest commit - the workdir is now used only to hold the entrypoint.sh script
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.
I think you are missing my point here. There was a /home/coder/project
directory before where users were supposed to mount their code from the host machine. You have simply replaced it with a new name. You should have left it as it was and created /home/coder/workdir
(or something more appropriately named) directory next to it where you can place your entrypoint script so it does not get overwritten. In my opinion, a better option would be to leave the directory as they were before your PR and place your entrypoint script in something like /usr/local/bin/
so you can refer to it without an absolute path from anywhere and have the least chance of being overwritten due to bind mounts.
DockerLaunchCommands.MD
Outdated
@@ -0,0 +1,14 @@ | |||
# Docker launch commands for coder |
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.
You might want to fix the title cases of headings and subheads to match with the style of the README file. Also, I personally do no like upper-case file extensions (as ignore patterns are easier if extensions are kept lower-cased) and CamelCased file names, but that could just me.
Not sure if this is a good idea, the reamde for fixuid says:
Not sure why. |
@nhooyr editors are generally used in development environment, unless someone deploys it on a server and makes it available to a larger user-base. |
|
I'm cleaning this up to make fixuid configurable via an entrypoint.sh script and disabled by default. If a user enables (via env var), it will log the warning message about non prod use. The user can disable this warning if the risk is acceptable (also via env var). I am testing this out and will update the PR asap. As a note, we believe the risk is acceptable in our environment, particularly with other controls we have in place, and the fact this will be a developer only tool |
Added the entrypoint script back in to make fixuid configurable. By default the container will run without invoking fixuid. If the user sets the FIXUID env var in the container it will set the coder user to UID:GID values passed by the `docker -u` cli argument. If the user also sets the FIXUID_QUIET env var, it will disable the warning message. Also cleaned up stuff based on comments on the PR
Dockerfile
Outdated
# We create first instead of just using WORKDIR as when WORKDIR creates, the user is root. | ||
RUN mkdir -p /home/coder/project | ||
RUN mkdir -p /home/coder/workdir | ||
copy entrypoint.sh /home/coder/workdir/ |
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.
copy entrypoint.sh /home/coder/workdir/ | |
COPY entrypoint.sh /home/coder/workdir/ |
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.
Docker instructions are written in upper case.
Dockerfile
Outdated
# We create first instead of just using WORKDIR as when WORKDIR creates, the user is root. | ||
RUN mkdir -p /home/coder/project | ||
RUN mkdir -p /home/coder/workdir |
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.
I think you are missing my point here. There was a /home/coder/project
directory before where users were supposed to mount their code from the host machine. You have simply replaced it with a new name. You should have left it as it was and created /home/coder/workdir
(or something more appropriately named) directory next to it where you can place your entrypoint script so it does not get overwritten. In my opinion, a better option would be to leave the directory as they were before your PR and place your entrypoint script in something like /usr/local/bin/
so you can refer to it without an absolute path from anywhere and have the least chance of being overwritten due to bind mounts.
@ibnesayeed - appreciate the feedback, but if you check my latest commit in the Dockerfile, the |
Removing the
|
I added it back in, but per the commit history the only reason this was needed in the past was because the Funnily, in testing, without the mkdir, the WORKDIR was still created as the coder user. Here's an example from a first start with fixuid disabled: coder@d13691a6f654:~$ cd /home/coder
coder@d13691a6f654:~$ ls -la
total 36
drwxr-xr-x 1 coder coder 4096 May 4 10:45 .
drwxr-xr-x 1 root root 4096 May 3 15:17 ..
-rw-r--r-- 1 coder coder 220 May 3 15:17 .bash_logout
-rw-r--r-- 1 coder coder 3771 May 3 15:17 .bashrc
drwxr-xr-x 3 coder coder 4096 May 4 10:45 .cache
drwxr-xr-x 3 coder coder 4096 May 4 10:45 .local
-rw-r--r-- 1 coder coder 807 May 3 15:17 .profile
drwxr-xr-x 4 coder coder 128 Apr 25 16:47 project
-rw------- 1 coder coder 1024 May 4 10:45 .rnd
drwxr-xr-x 1 coder coder 4096 May 3 21:08 workdir <== THIS
coder@d13691a6f654:~$ When fixuid is enabled it will recursively chown the home directory of the user, including workdir, making this even less important. |
@satlus in that case I would get rid of that That said, I would perhaps
|
…me/coder The entrypoint.sh script is now copied to /usr/local/bin, companion to the code-server binary. The WORKDIR directory is no longer created as nothing is copied to it. Lastly, we've modified the external volume mount to export the entire home directory. This will avoid UFS layer creation for .cache and .local directory writes, which appears to happen by default.
@ibnesayeed Good suggestions, implemented. In testing I noticed that we'd have to provide explicit volume mounts for /home/coder/.cache and /home/coder/.local when enabling non-root use. This seems less than ideal from a usability perspective, and as it has the effect of writing transient data inside of the container by default. To mitigate this I went ahead and changed the Would definitely appreciate the committers commenting here - it seems this is getting a bit broader in scope than originally anticipated. |
WORKDIR /home/coder/project | ||
# Setup our entrypoint | ||
COPY entrypoint.sh /usr/local/bin/ | ||
RUN sudo chmod +x /usr/local/bin/entrypoint.sh |
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.
These two COPY
and RUN
commands can be moved above the USER
command so that the entrypoint.sh
file is copied as root under the /usr/local/bin/
directory and chmod
needs no sudo
.
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.
Good call, will be in the next commit
@@ -9,7 +9,7 @@ | |||
|
|||
Try it out: | |||
```bash | |||
docker run -it -p 127.0.0.1:8443:8443 -v "${PWD}:/home/coder/project" codercom/code-server --allow-http --no-auth | |||
docker run -it -p 127.0.0.1:8443:8443 -v "${PWD}/code-server:/home/coder" codercom/code-server --allow-http --no-auth |
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.
I am not too sure about this change. Are we assuming that there will be code-server
available under ${PWD}/
?
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.
It will be created if it doesn't exist - I think it's better than polluting the $PWD with the rnd, .local and cache as it does now.
|
||
# This assures we have a volume mounted even if the user forgot to do bind mount. | ||
# So that they do not lose their data if they delete the container. | ||
VOLUME [ "/home/coder/project" ] | ||
VOLUME [ "/home/coder" ] |
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 we change this to:
VOLUME [ "/home/coder" ] | |
VOLUME [ "/home/coder/project", "/home/coder/.cache", "/home/coder/.local" ] |
Instead of creating a volume for the whole home directory?
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.
Yes - will test and update tomorrow
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.
Saw some odd things after testing this change and i'm not sure we should go this route. The first is that if we decide to bind mount /home/coder, to have the full home directory on the local filesystem, duplicate folders are created:
code-server$ !find
find ./code-server/ -maxdepth 1 -type d
./code-server/
./code-server//project,
./code-server//.local
./code-server//.local,
./code-server//.cache,
./code-server//.cache
The second is comma suffixed directories are dupes and don't have data. I'm wondering if this is a side-effect of us bind mounting a parent path of previously defined volume containers. Taking a step back, do we really want to create three anonymous volume containers here by default? I'd rather just have the home directory be portable as an anonymous volume container, and recommend a compose file or docker run commands where the volumes are explicitly created for this purpose.
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.
Some changes.
@@ -34,20 +34,29 @@ RUN locale-gen en_US.UTF-8 | |||
# configured in /etc/default/locale so we need to set it manually. | |||
ENV LC_ALL=en_US.UTF-8 | |||
|
|||
RUN adduser --gecos '' --disabled-password coder && \ | |||
RUN addgroup --gid 1000 coder && \ |
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.
by default the first user to be made in Docker build is 1000 already, I don't see the need for extra steps.
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.
by default the first user to be made in Docker build is 1000 already
This is false. Docker does not enforce a default uid of 1000. The Node images that are being used as a base image however do create a node
user of 1000:1000.
That said, this doesn't need to create a new user for 1000:1000, I'm not sure what is up with all the other options in addition to that. The intention appears to be replacing node
with coder
which is used later on. Looks like they're referencing this addition from fixuid
installation docs without much thought..
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd | ||
|
||
RUN USER=coder && \ |
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.
use su -c
instead or gosu
.
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.
gosu is actually preferred as it mirrors --user
flag exactly and fixes weird tty and signal forwarding issues. See https://github.com/tianon/gosu. FYI Tianon creates a lot of official Docker Images on hub.docker.com.
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.
@sr229 @SuperSandro2000 this is setting temporary env vars for USER
and GROUP
as per instructions of how to use fixuid
, which afaik is only intended for the last line in the command:
printf "user: $USER\ngroup: $GROUP\n" > /etc/fixuid/config.yml
Rather than add an additional config file externally. Has nothing to do with gosu
, which you say to use "instead of", but there is no mention of gosu
in the Dockerfile you're reviewing...?(unless there was in prior commit history, doesn't seem like it since the review from @sr229 is June 18th and last commit was May 6th)
Just in case there is any confusion, fixuid
is for remapping existing file ownership of the config user:group to the given uid:gid from --user
flag in docker run
command. gosu
is intended for dropping privileges from root to another user to run a command as instead:
The core use case for
gosu
is to step down fromroot
to a non-privileged user during container startup (specifically in theENTRYPOINT
, usually). - 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.
@polarathene I was referring to sr229.
As an alternative solution to fixuid, you can differ the creation of the coder user and group to the entrypoint.sh script.
|
We looked at a similar strategy but it won't work if we want init.d in the container to be non-root (the entire point of passing -u to docker at runtime). Unless I'm missing something, this won't satisfy the requirement. |
Any chance of this PR landing anytime soon? I'm facing this issue and would love to have a solution |
@carlos-sarmiento from what info I can gather, PRs are on hold until we can merge v2. If you really want to test, |
@sr229 is there any idea on when v2 might land? |
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.
@satlus can you try opening a largish project? Does fixuid slow container startup down considerably? iirc that's why I remember not integrating it.
Happy to do some testing here, but I can see how fixuid's behavior could slow startup significantly (basically to recursively chown all files from /) . To mitigate this on the fixuid side we can restrict paths to recurse via fixuid's 'path' env or yaml config (https://github.com/boxboat/fixuid#specify-paths-and-behavior-across-devices), but if the project directory is large it may be a fixed cost we'd have to eat. Two things to consider (1) it's opt in via env var, in which case there is zero change to the default behavior, (2) if given the option of not running at all in a restricted environment, and having something work with slow startup time, some users may be willing tolerate the slow startup. We should at least warn users of the side effect in this case. That aside, I will find some time in the next few days to test things out. I'm also curious if after the initial fixuid chown we can get away without invoking it on subsequent container starts. |
If you run with UID 1000, things should always just work as the image itself always sets itself to run with uid 1000 by default. You can always access root with Is that not enough for your use case? |
I guess what I'm really suggesting is ensuring your files/folders outside the container are also UID 1000 as that's the only real way to do this without jank with Docker. |
The other being using user namespacing and explicitly mapping some external UID to UID 1000 inside the container. See https://docs.docker.com/engine/security/userns-remap/ |
What if I am not user 1000 on the base system? I won't be able to read the files. What about systems with multiple users? Linuxserver,io has solved the issue using s6_overlay. |
You're using docker, so you should be accessing the files inside the container. If you need to still access them outside the container, use sudo or start the container with the entrypoint as bash. It's the way docker works best right now. |
@code-asher Let's cherry-pick this PR then pull a new PR that adds this PR's changes. |
I'm gonna take care of this myself in the coming days. Thank you @satlus and @ibnesayeed for your work. |
Describe in detail the problem you had and how this PR fixes it
Our environment restricts docker execution to images that can only with a non root user
docker run -u $UID...
. This PR adds boxboat/fixuid to allow for runtime mapping of host->container UIDIs there an open issue you can link to?
#439