My goal is to understand:
How Docker can do $ su to nonexistent user? e.g $ docker run -it --rm -u 10001:10001 alpine:3.16
Is it safe to use $ docker run -u or USER variable in Dockerfile with a nonexistent user? I mean, is it possible if the container is compromised, they can do something nasty because the user technically does not exist?
The problem is, I can't achieve it even with root. What I've tried:
$ su nobody <- the shell is /sbin/nologin. So, it throws This account is not available.
$ docker run -it --rm -u nobody:nobody alpine:3.16, it works.
$ su nonexistent <- no record in /etc/passwd. So, it throws su: unknown user nonexistent.
$ docker run -it --rm -u 10001:10001 alpine:316, it works.
$ docker run -it --rm -u nonexistent:nonexistent alpine:3.16, the docker daemon will throw docker: Error response from daemon: unable to find user nonexistent: no matching entries in passwd file., it is expected.
Dockerfile
FROM alpine:3.16
USER 10001:10001
How Docker can do $ su...
Usually it can't. A typical Docker container won't have a root password or any other password set, and it may not even have the su binary.
This isn't typically a problem since a Docker container only runs a single process, and you can explicitly set the user ID when you start the container with...
docker run -it --rm -u 10001:10001 alpine:3.16
The important thing about Unix user and group IDs is their numeric value. For example, there's nothing special about the user name root, but if the current user has the numeric user ID 0 then it has special privileges. Similarly, this command sets both the user and group IDs to 10001; it can write files if they have user- or group-write permission and are owned by exactly that numeric user or group ID. There's no requirement that a user or group "exist" per se.
Particularly in a Docker context, this can come up routinely with bind-mounted host directories. The container's numeric user ID needs to match the numeric user ID that owns the files on the host, but there's no requirement to "create the user".
Is it safe to use $ docker run -u or USER variable in Dockerfile with a nonexistent user?
Yes. The two things that are important are (a) whether or not the numeric user ID is 0 and (b) whether the numeric user ID matches the ownership of files in the container or mounted volumes. If the numeric user ID doesn't match then the user won't be able to overwrite files.
It's possible some application code might try to find out the current user name and get confused when it doesn't exist. If you're using bash as a debugging shell it might print a "no such user" complaint as part of the prompt, but this is totally cosmetic.
A more specific statement: it's safe to do something like
docker run -u $(id -u):$(id -g) -v "$PWD:/data" ...
to both set the numeric user and group ID of the container to match the host, and to mount the current directory into the container somewhere. There's no requirement to "create the user" before you do, and it's not an especially good practice to build an image that has a specific host user ID built in.
Related
When do we need to add -u $(id -u):$(id -g) in docker run command?
I see that it is user id and group ip mapping but I want to understand this better.
One reason you'd want to run the container under the same UID and GID as your user is so that any files created by the container in the host file system will be owned by you.
Take for instance this command, that creates a file called test.txt in the current directory on the host
docker run --rm -v $(pwd):/app ubuntu touch /app/test.txt
In the host file system, that file will be owned by root.
By running the container with the same UID and GID as your user, the file will be owned by you instead
docker run --rm -v $(pwd):/app -u $(id -u):$(id -g) ubuntu touch /app/test2.txt
Brief docker background
Docker starts containers as a root user. The root user has almost full privileged access to the state of the container. Any processes running as that user inherit those permissions.
When do we need user and group?
It follows that if there’s a bug in one of those processes, it might damage the container. There are ways to limit the damage, but the most effective way to prevent these types of issues is not to use the root user. So we use the group and user.
RUN groupadd -r -g 2200 example && useradd -rM -g example -u 2200 example
Docker supports isolating the USR namespace. By default, user and group IDs inside a container are equivalent to the same IDs on the host machine. When the user namespace is enabled, user and group IDs in the container are remapped to IDs that do not exist on the host.
Hope this helps you!
I'm trying to let a docker container access a letsencrypt certificate from the host file system.
I do not want to run the docker container as root, but rather as a user with very specific access rights.
Neither do I want to change the permissions of the certificate.
All I want, is for the given user, to have access to read the certificate inside the docker container.
The certificate has the following setup:
-rw-r----- 1 root cert-group
The user who's going to run the docker container, is in the cert-group:
uid=113(myuser) gid=117(myuser) groups=117(myuser),999(cert-group),998(docker)
This works as long as we're on the host - I am able to read the file as expected with the user "myuser".
Now I want to do this within a docker container with the certificate mounted as a volume.
I have done multiple test cases, but none with any luck.
A simple docker-compose file for testing:
version: '3.7'
services:
test:
image: alpine:latest
volumes:
- /etc/ssl/letsencrypt/cert.pem:/cert.pem:ro
command: >
sh -c 'ls -l / && cat /etc/passwd && cat /etc/group && cat /cert.pem'
user: "113:117"
restart: "no"
This ouputs a lot, but most important is:
test_1 | -rw-r----- 1 root ping 3998 Jul 15 09:51 cert.pem
test_1 | cat: can't open '/cert.pem': Permission denied
test_1 | ping:x:999:
Here I assume that "ping" is an internal group for docker alpine, however, im getting some mixed information about how this collaborates with the host.
From this article https://medium.com/#mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf my takeaway is, that there's a single kernel handling all permissions (the host) and therefore if the same uid and gid is used, the permissions would inherit from the host. However, even though that the running user is 113:117, which on the host is part of the group 999 it still doesnt give me access to read the file.
Next I found this article https://medium.com/#nielssj/docker-volumes-and-file-system-permissions-772c1aee23ca where especially this bullet point caught my attention:
The container OS enforces file permissions on all operations made in
the container runtime according to its own configuration. For example,
if a user A exists in both host and container, adding user A to group
B on the host will not allow user A to write to a directory owned by
group B inside the container unless group B is created inside the
container as well and user A is added to it.
This made me think, that maybe a custom Dockerfile was needed, to add the user inside docker, and make the user part of 999 (which is known as ping as earlier stated):
FROM alpine:latest
RUN adduser -S --uid 113 -G ping myuser
USER myuser
Running this gives me the exact same result, now with myuser appended to passwd though:
test_1 | myuser:x:113:999:Linux User,,,:/home/myuser:/sbin/nologin
This is just a couple of things that I've tried.
Another is syncing /etc/passwd and /etc/group with volumes found in some other blog
volumes:
- /etc/passwd:/etc/passwd
- /etc/group:/etc/group
This makes it visually look correct inside the container, but it doesnt change the end result - still permission denied.
Any help or pointers in the right direction would be really appreciated since I'm running out of ideas.
Docker containers do not know the uid/gid of the user running the container on the host. All requests to run containers go through the docker socket, and then to the docker engine that is often running as root, and no uid/gid's are passed in those API calls. The docker engine is just running the container as the user specified in the Dockerfile or as part of the container create command (in this case, from the docker-compose.yml).
Once inside the container, the mapping from uid/gid to names is done with the /etc/passwd and /etc/group file that is inside the container. Importantly, at the filesystem level, uid/gid values are not being mapped between the container and the host (with the exception of user namespaces, but if implemented properly, that would only make this problem worse). And all filesystem operations happen at the uid/gid level, not based on names. So when you do a host volume mount, the uid/gid's are passed directly through.
The issue you are encountering here is how you are telling the container to pick the uid/gid to run the container processes. By specifying user: "113:117" you have told the container to not only specify the uid (113), but also the gid (117) of the process. When that's done, none of the secondary groups from /etc/group are assigned to the user. To get those secondary groups assigned, you want to only specify the uid, user: "113", which will then lookup the group assignments from the /etc/passwd and /etc/group file inside the container. E.g.:
user: "113"
Unfortunately, the lookup for group membership is done by docker before any volumes are mounted, so you have the following scenario.
First, create an image with an example user assigned to a few groups:
$ cat df.users
FROM alpine:latest
RUN addgroup -g 4242 group1 \
&& addgroup -g 8888 group2 \
&& adduser -u 1000 -D -H test \
&& addgroup test group1 \
&& addgroup test group2
$ docker build -t test-users -f df.users .
...
Next, run that image, comparing the id on the host to the id inside the container:
$ id
uid=1000(bmitch) gid=1000(bmitch) groups=1000(bmitch),24(cdrom),25(floppy),...
$ docker run -it --rm -u bmitch -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
docker: Error response from daemon: unable to find user bmitch: no matching entries in passwd file.
Woops, docker doesn't see the entry from /etc/passwd, lets try with the test user we created in the image:
$ docker run -it --rm -u test -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
uid=1000(bmitch) gid=1000(bmitch) groups=4242,8888
That works, and assigns the groups from the /etc/group file in the image, not the one we mounted. We can also see that uid works too:
$ docker run -it --rm -u 1000 -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
uid=1000(bmitch) gid=1000(bmitch) groups=4242,8888
As soon as we specify the gid, the secondary groups are gone:
$ docker run -it --rm -u 1000:1000 -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
uid=1000(bmitch) gid=1000(bmitch)
And if we run without overriding the /etc/passwd and /etc/group file, we can see the correct permissions:
$ docker run -it --rm -u test test-users:latest id
uid=1000(test) gid=1000(test) groups=4242(group1),8888(group2)
Likely the best option is to add a container user with the group membership matching the uid/gid values from the host. For host volumes, I've also solved this problem with a base image that dynamically adjusts the user or group inside the container to match the uid/gid of the file mounted in a volume. This is done as root, and then gosu is used to drop permissions back to the user. You can see that at sudo-bmitch/docker-base on github, specifically the fix-perms script that I would run as part of an entrypoint.
Also, be aware that mounting the /etc/passwd and /etc/group can break file permissions of other files within the container filesystem, and this user may have access inside that container that is not appropriate (e.g. you may have special access to the ping command that gives the ability to modify files or run ping commands that a normal user wouldn't have access to). This is why I tend to adjust the container user/group rather than completely replace these files.
Actually your solution is not wrong. I did the same with few differences.
This is my Dockerfile:
FROM alpine:latest
RUN addgroup -S cert-group -g 117 \
&& adduser -S --uid 113 -G cert-group myuser
USER myuser
And my docker-compose.yml:
version: '3.7'
services:
test:
build:
dockerfile: ./Dockerfile
context: .
command: >
sh -c 'ls -l / && cat /etc/passwd && cat /etc/group && cat /cert.pem'
volumes:
- "/tmp/test.txt:/cert.pem:ro"
restart: "no"
My '/tmp/test.txt' is assigned to 113:117.
IMHO, I think the problem in your docker-compose.yml that doesn't use your image. You should remove the image: and add build:
I have gone through the same issue today and luckily, the below solution helped me.
"Add :Z to your volumes mounts"
Reference: https://github.com/moby/moby/issues/41202
Note: Unfortunately It's issue with only Centos, I didn't face any problem with Ubuntu.
What is the difference between docker run parameters:
-u, --user=""
Sets the username or UID used and optionally the groupname or GID for the specified command.
The followings examples are all valid:
--user [user | user:group | uid | uid:gid | user:gid | uid:group ]
Without this argument the command will be run as root in the container.
and
--group-add=[]
Add additional groups to run as
?
docker run --user=demo_user <image_name> <command> runs a container with the given command as demo_user
docker run --user=demo_user:group1 <image_name> <command> runs a container with the given command as demo_user whose primary group is set to group1
docker run --user=demo_user:group1 --group-add group2 <image_name> <command> runs a container with the given command as demo_user whose primary group is set to group1 and group2 as secondary group of the user
NOTE: users and groups used for these options MUST have been created in the image of which we are creating a container.
If --group-add option alone is specified without --user and the image does NOT have any user declared(user should have been created but not declared via USER instruction in Dockerfile from which the image got created), group modifications happen to the root user in the container.
If --group-add option alone is specified without --user and the image does have the user declared( via USER instruction in Dockerfile from which the image got created), group modifications happen to the declared user in the container.
When you create a Docker image, you can also create users and groups inside it. Those options allow you to connect as a specific user (-u) and with additional groups (--group-add).
In other words, when you execute a process in a Docker container, you do so as the provided user, and its groups (defined in the system). You can tell the system that the current user has addition groups by using the --group-add flag, for the process' lifetime.
Check out the documentation here: https://docs.docker.com/engine/reference/run/#/additional-groups
$ docker run --rm --group-add audio --group-add nogroup --group-add 777 busybox id
uid=0(root) gid=0(root) groups=10(wheel),29(audio),99(nogroup),777
When start a docker container as user with name 'username1' in group 'usergroup1'.
And that container has files/folders on the local file system with volume:
eg.
$username1>docker run -v /homes/username1/output:output outputter
The files are created with root as owner.
What do i need to do in the Dockerfile or startup options to make sure the file rigths in the output folder are the same as the localuser:group, in this case username1:usergroup1?
As explained in this project:
By default, our docker containers run as the root user. Files created or modified by the container will thus become owned by the root user, even after quitting the container.
To avoid this problem, it is necessary to run the container using a non-root user.
If the host machine user has a UID other than 1000 (or 0, for root), the user should specify their UID when running docker, e.g.
docker run -d -p 8787:8787 -v $(pwd):/home/$USER/foo \
-e USER=$USER -e USERID=$UID rocker/rstudio
to avoid changing the permissions in the linked volume on the host
Here that works because that project Dockerfile, when starting the container, creates a user with the same uid (name is not important)
## (Docker cares only about uid, not username; diff users with same uid = confusion)
if [ "$USERID" -ne 1000 ]
## Configure user with a different USERID if requested.
then
echo "creating new $USER with UID $USERID"
useradd -m $USER -u $USERID
mkdir /home/$USER
chown -R $USER /home/$USER
You are going to have to wait for user namespace support, hopefully later this year.
I have a docker container with a -v /home/dan:/home/dan:rw. When the container writes files to /home/dan, the files are owned by root in the host filesystem. Is there a way to make it so that files written from the container to the mounted volume are owned by some arbitrary user on the host filesystem?
As François Zaninotto has pointed out, the user id can be used.
This can be done by using the -u switch for the docker run command.
For example:
docker run -v /home/dan:/home/dan -u `id -u $USER` IMAGE
EDIT: this has changed since my original answer which said it couldn't be done. As per answer of Mandark:
This can be done by using the -u switch for the docker run command.
For example:
docker run -v /home/dan:/home/dan -u `id -u $USER` IMAGE
A follow up to mandark answer - I would say it's also good to include the user group otherwise you will end up with stuff belonging to user: USER and group: root. To achive user:user just pass in group id as well, for example:
docker run -v /home/dan:/home/dan -u `id -u $USER`:`id -g $USER` IMAGE
# if it's for the current user, then you can omit the $USER env var
docker run -v /home/dan:/home/dan -u `id -u`:`id -g` IMAGE
It's possible. It's hard to automate, but it's possible. Here is the process:
in the host, determine the current user id and group id
in the docker container, run a shell script to:
add a new group with the group id from the host
add a new user with the same user id from the host (and belonging to the group just created)
sudo as this new user
Now, each file generated inside the container will be using the right user id and group id, and the host will attach them to your user.
I've written a tool to automate that using make, it's called make-docker-command. Hope this helps.