-
Notifications
You must be signed in to change notification settings - Fork 6.2k
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
@ibnesayeed
ibnesayeed
May 3, 2019
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.
@ibnesayeed
ibnesayeed
May 3, 2019
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
@ibnesayeed
ibnesayeed
May 3, 2019
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:
fixuid should only be used in development Docker containers. DO NOT INCLUDE in a production container image
Not sure why.
ibnesayeed
commented
May 3, 2019
Not sure if this is a good idea, the reamde for fixuid says:
fixuid should only be used in development Docker containers. DO NOT INCLUDE in a production container image
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.
ibnesayeed
commented
May 3, 2019
@nhooyr: Not sure why.
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
@ibnesayeed
ibnesayeed
May 3, 2019
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.
@ibnesayeed
ibnesayeed
May 4, 2019
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
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 VOLUME
bind mount is pointed to /home/coder/project, as per the original in master. The WORKDIR
is a different directory, where the entrypoint.sh is copied (and only used for that purpose). What's the issue?
ibnesayeed
commented
May 4, 2019
@satlus: @ibnesayeed - appreciate the feedback, but if you check my latest commit in the Dockerfile, the
VOLUME
bind mount is pointed to /home/coder/project, as per the original in master. TheWORKDIR
is a different directory, where the entrypoint.sh is copied (and only used for that purpose). What's the issue?
Removing the mkdir
command and only relying on WORKDIR
has the following side effect as per an existing comment:
# We create first instead of just using WORKDIR as when WORKDIR creates, the user is root.
I added it back in, but per the commit history the only reason this was needed in the past was because the WORKDIR
and VOLUME
paths resolved to the same location - /home/coder/project. This is no longer the case (as it should be), and workdir is only used to house the entrypoint. There really is no need for this going forward IMO.
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.
ibnesayeed
commented
May 4, 2019
@satlus in that case I would get rid of that mkdir
command and the comment that says why it was important. Personally, I know WORKDIR
and some other Docker commands run in the scope of the user if a USER
is declared in a prior instruction, but the comment was confusing me.
That said, I would perhaps COPY
the entrypoint.sh
file into /usr/local/bin/
. This would:
- No need to create yet another directory specifically for this
- Minimize the chances being overwritten if the user chooses the mount the whole HOME directory inside
- The
ENTRYPOINT
instruction can be changed to["dumb-init", "entrypoint.sh", "code-server"]
without the need of absolute or relative path
...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.
@satlus in that case I would get rid of that
mkdir
command and the comment that says why it was important. Personally, I knowWORKDIR
and some other Docker commands run in the scope of the user if aUSER
is declared in a prior instruction, but the comment was confusing me.That said, I would perhaps
COPY
theentrypoint.sh
file into/usr/local/bin/
. This would:
@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 VOLUME
mount to export the entire /home/coder directory (and updated docker CLI run examples in the README). This seems like smarter way to ensure config and runtime data are separated from the container image appropriately.
Would definitely appreciate the committers commenting here - it seems this is getting a bit broader in scope than originally anticipated.
@ibnesayeed
ibnesayeed
May 6, 2019
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
@ibnesayeed
ibnesayeed
May 6, 2019
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.
@ibnesayeed
ibnesayeed
May 6, 2019
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:
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.
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.
@polarathene
polarathene
Nov 16, 2019
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..
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.
@polarathene
polarathene
Nov 16, 2019
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.
ptoulouse
commented
Jun 19, 2019
As an alternative solution to fixuid, you can differ the creation of the coder user and group to the entrypoint.sh script.
- Create the user based on a user id and group id environment variable (often seen: PUID, PGID) with default values of 1000 and 1000: PUID=${PUID:-1000}, PGID=${PGID:-1000}
- chown -R coder:coder /whatever/directory
- runuser -u coder -- code-server-command
As an alternative solution to fixuid, you can differ the creation of the coder user and group to the entrypoint.sh script.
- Create the user based on a user id and group id environment variable (often seen: PUID, PGID) with default values of 1000 and 1000: PUID=${PUID:-1000}, PGID=${PGID:-1000}
- chown -R coder:coder /whatever/directory
- runuser -u coder -- code-server-command
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.
carlos-sarmiento
commented
Aug 2, 2019
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, chinodesuuu/coder:satlus
is a feature parity replica of this PR.
carlos-sarmiento
commented
Aug 3, 2019
@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.
@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.
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 UID
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 sudo
.
https://github.com/cdr/code-server/blob/22058c5f861d6f39bc42a768d5cb65ffdf696ed1/Dockerfile#L46
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/
ptoulouse
commented
Sep 20, 2019
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.
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.
What if I am not user 1000 on the base system? I won't be able to read the files.
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