How to launch container with user namespace configuration? - docker

In the below docker file, base image(jenkins/jenkins) is providing a user jenkins with UID 1000 and GID 1000, within container.
FROM jenkins/jenkins
# Install some base packages
# Use non-privileged user provided by base image
USER jenkins # with uid 1000 and GID 1000
# Copy plugins and other stuff
On the docker host(EC2 instance), we also have similar UID & GID created,
$ groupadd -g 1000 jenkins
$ useradd -u 1000 -g jenkins jenkins
$ mkdir -p /abc/home_folder_for_jenkins
$ chown -R jenkins:jenkins /abc/home_folder_for_jenkins
to make sure, container can write files to /abc/home_folder_for_jenkins in EC2 instance.
Another aspect that we need to take care in same EC2 instance, is to run containers(other than above container) to run in non-privileged mode.
So, below configuration is performed on docker host(EC2):
$ echo dockremap:165536:65536 > /etc/subuid
$ echo dockremap:165536:65536 > /etc/subgid
$ echo '{"debug":true, "userns-remap":"default"}' > /etc/docker/daemon.json
This dockremap configuration is not allowing jenkins to start and docker container goes in Exited state:
$ ls -l /abc/home_folder_for_jenkins
total 0
After removing docker remap configuration, everything work fine.
Why dockremap configuration not allow the jenkins container to run as jenkins user?

I'm actually fighting with this because it seems not very portable but this is the best I found. As said above on your docker host the UID/GID are the ones from the container + the value in /etc/subuid & /etc/subgid.
So your "container root" is 165536 on your host and your user jenkins is 166536 (165536 + 1000).
To come back to your example what you need to do is
$ mkdir -p /abc/home_folder_for_jenkins
$ chown -R 166536:166536 /abc/home_folder_for_jenkins

User namespaces offset the UID/GID of the user inside the container, and any files inside the container. There is no mapping from the UID/GID inside the container to the external host UID/GID (that would defeat the purpose). Therefore, you would need the offset the UID/GID of the directory being created, or just use a named volume and let docker handle this for you. I believe that UID/GID on the host would be 166536 (165536 + 1000) (I may have an off by one in there, so try opening the directory permissions if this still fails and see what gets created).

Related

Docker(containers) cgroup/namespace setup vs running Dockerfile commands as root?

From my understanding, docker sets up the required cgroup's and namespace's so containers(i.e container processes) run in isolation (isolated environment on the host system) and have limited permissions and access to the host system. So, even if the process is running as root in the container, it will not have root access on the host system.
But from this article: processes-in-containers-should-not-run-as-root, i see that it is still possible for a container process running as root to access the host files which are only accessible to root on the host system.
On host system:
root#srv:/root# ls -l
total 4
-rw------- 1 root root 17 Sep 26 20:29 secrets.txt
Dockerfile -
FROM debian:stretch
CMD ["cat", "/tmp/secrets.txt"]
On running corresponding image of above Dockerfile,
marc#srv:~$ docker run -v /root/secrets.txt:/tmp/secrets.txt <img>
top secret stuff
If, top secret stuff is readable, how is it possible. Then what is the point of container isolation. What am i missing, seems there is something more I am missing.
(has it to do with how i use docker run, by default are all permissions/capabilities given to the container based on the user running the docker run command.
A container can only access the host filesystem if the operator explicitly gives it access. For example, try without any docker run -v options:
docker run \
--rm \ # clean up the container when done
-u root \ # explicitly request root user
busybox \ # image to run
cat /etc/shadow # dumps the _container's_ password file
More generally, the rule (on native Linux without user namespace remapping) is that, if files are bind-mounted from the host into a container, they are accessible if the container's numeric user or group IDs match the file's ownership and permissions. If a file is owned by uid 1000 on the host with mode 0600, it can be read by uids 0 or 1000 in the container, regardless of the corresponding container and host users' names.
The corollary to this is that anyone who can run any docker run command at all can pretty trivially root the entire host.
docker run \
--rm \
-u root \
-v /:/host \ # bind-mount the host filesystem into the container
busybox \
cat /host/etc/shadow # dumps the host's encrypted password file
The root user in a container is further limited by Linux capabilities: without giving special additional Docker options, even running as root, a container can't change filesystem mounts, modify the network configuration, load kernel modules, reboot the host, or do several other extra-privileged things. (And it's usually better to do these things outside a container than to give extra permission to Docker; don't casually run containers --privileged.)
It's still generally better practice to run containers as non-root users. The user ID doesn't need to match any user ID in particular, it just needs to not be 0 (matching a specific host uid isn't portable across hosts and isn't recommended). The files in the container generally should be owned by root, so they can't be accidentally overwritten.
FROM debian
# Create the non-root user
RUN adduser --system --no-create-home nonroot
# Do the normal installation, as root
COPY ... # no --chown option
RUN ... # does not run chown either
# Specify the non-root user only for the final container
EXPOSE 12345
USER nonroot
CMD the main container command
If the container does need to read or (especially) write host files, bind-mount the host directory into some data-specific directory in the container (do not overwrite the application code with this mount) and use the docker run -u option to specify the host uid that the container needs to run as. The user does not specifically need to exist in the container's /etc/passwd file.
docker run \
-v "$PWD:/app/data" \ # bind-mount the current directory as data
-u $(id -u) \ # specify the user ID to use
...

Bind mounts created using rootless docker have a weird uid on the host machine. How can I delete these folders?

I have the following docker-compose.yml file which creates a bind mount located in $HOME/test on the host system:
version: '3.8'
services:
pg:
image: postgres:13
volumes:
- $HOME/test:/var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=pass
- PGUSER=postgres
I bring up the container and inspect the permissions of the bind mount directory:
$ docker-compose up -d
$ ls -l ~
drwx------ 19 4688518 usertest 4096 Mar 11 17:06 test
The folder ~/test is created with a different uid in order to prevent accidental manipulation of this folder outside of the container. But what if I really do want to manipulate it? For example, if I try to delete the folder, I get a permission denied error as expected:
$ rm ~/test -rf
rm: cannot remove '/home/usertest/test': Permission denied
I suspect that I need to change uids using the newuidmap command somehow, but I'm not sure how to go about that.
How can I delete these folders?
But what if I really do want to manipulate it?
Using Docker, you can:
Run a command in the container as a specific user using the same UID (such as rm or sh), for example:
# Run shell session using your user with docker-compose
# You can then easily manipulate data
docker-compose exec -u 4688518 pg sh
# Run command directly with docker
# Docker container name may vary depending on your situation
# Use docker ps to see real container name
docker exec -it -u 4688518 stack_pg_1 rm -rf /var/lib/postgresql/data
Similar to previous one, you can run a new container with:
# Will run sh by default
docker run -it -u 4688518 -v $HOME/test:/tmp/test busybox
# You can directly delete data with
docker run -it -u 4688518 -v $HOME/test:/tmp/test busybox rm -rf /tmp/test/*
This may be suitable if your pg container is stopped or deleted. Docker image itself does not need to be the same as the one run by Docker Compose, you only need to specify proper user UID.
Note: you may not be able to delete folder using rm -rf /tmp/test as user 4688518 may not have writing permission on /tmp folder to do so, hence the use of /tmp/test/*
Use any of the above, but using root user such as -u 0 or -u root
Without using Docker, you can effectively run sudo command as suggested by other answer, or even temporarily change permission of said folder then change it back. However, from experience, when manipulating Docker-related data it's easier and less error-prone to user Docker itself.
Dealing with user ids in docker is tricky business because docker containers share the same kernel with the host operating system (at least on linux). Consequently, any files that the container creates in the bind mount with a given uid will have the same uid on the host system.
Whenever the uid used by the container (let's say it's 2222) is different from your own uid (or you don't have write access to files owned by 2222), you won't be able to delete the folder. The easy workaround is to run sudo rm -rf ~/test.
Edit: If the user does not have admin rights, you can still give them rights to modify the generated files like so.
# Create a directory that the users can write in.
mkdir workspace
# Change the owner to the group of users that should have access (3333).
sudo chown -R 2222:3333 workspace
# Give group write access.
sudo chmod -R g+w workspace
# Make sure that all users that should have write access are in group 3333.
Then you can run the container using
docker run --rm -u `id -u`:3333 -v `pwd`/workspace:/workspace \
-w /workspace alpine:latest touch myfile
which creates myfile in the workspace folder with the right permissions so your users can delete the file again.

how to correctly use system user in docker container

I'm starting containers from my docker image like this:
$ docker run -it --rm --user=999:998 my-image:latest bash
where the uid and gid are for a system user called sdp:
$ id sdp uid=999(sdp) gid=998(sdp) groups=998(sdp),999(docker)
but: container says "no"...
groups: cannot find name for group ID 998
I have no name!#75490c598f4c:/home/myfolder$ whoami
whoami: cannot find name for user ID 999
what am I doing wrong?
Note that I need to run containers based on this image on multiple systems and cannot guarantee that the uid:gid of the user will be the same across systems which is why I need to specify it on the command line rather than in the Dockerfile.
Thanks in advance.
This sort of error will happen when the uid/gid does not exist in the /etc/passwd or /etc/group file inside the container. There are various ways to work around that. One is to directly map these files from your host into the container with something like:
$ docker run -it --rm --user=999:998 \
-v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro \
my-image:latest bash
I'm not a fan of that solution since files inside the container filesystem may now have the wrong ownership, leading to potential security holes and errors.
Typically, the reason people want to change the uid/gid inside the container is because they are mounting files from the host into the container as a host volume and want permissions to be seamless across the two. In that case, my solution is to start the container as root and use an entrypoint that calls a script like:
if [ -n "$opt_u" ]; then
OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:)
NEW_UID=$(stat -c "%u" "$1")
if [ "$OLD_UID" != "$NEW_UID" ]; then
echo "Changing UID of $opt_u from $OLD_UID to $NEW_UID"
usermod -u "$NEW_UID" -o "$opt_u"
if [ -n "$opt_r" ]; then
find / -xdev -user "$OLD_UID" -exec chown -h "$opt_u" {} \;
fi
fi
fi
The above is from a fix-perms script that I include in my base image. What's happening there is the uid of the user inside the container is compared to the uid of the file or directory that is mounted into the container (as a volume). When those id's do not match, the user inside the container is modified to have the same uid as the volume, and any files inside the container with the old uid are updated. The last step of my entrypoint is to call something like:
exec gosu app_user "$#"
Which is a bit like an su command to run the "CMD" value as the app_user, but with some exec logic that replaces pid 1 with the "CMD" process to better handle signals. I then run it with a command like:
$ docker run -it --rm --user=0:0 -v /host/vol:/container/vol \
-e RUN_AS app_user --entrypoint /entrypoint.sh \
my-image:latest bash
Have a look at the base image repo I've linked to, including the example with nginx that shows how these pieces fit together, and avoids the need to run containers in production as root (assuming production has known uid/gid's that can be baked into the image, or that you do not mount host volumes in production).
It's strange to me that there's no built-in command-line option to simply run a container with the "same" user as the host so that file permissions don't get messed up in the mounted directories. As mentioned by OP, the -u $(id -u):$(id -g) approach gives a "cannot find name for group ID" error.
I'm a docker newb, but here's the approach I've been using in case it helps others:
# See edit below before using this.
docker run --rm -it -v /foo:/bar ubuntu:20.04 sh -c "useradd -m -s /bin/bash $USER && usermod -a -G sudo $USER && su - $USER"
I.e. add a user (useradd) with a matching name, make it sudo (usermod), then open a terminal with that user (su -).
Edit: I've just found that this causes a E: List directory /var/lib/apt/lists/partial is missing. - Acquire (13: Permission denied) error when trying to use apt. Using sudo gives the error -su: sudo: command not found because sudo isn't install by default on the image I'm using. So the command becomes even more hacky and requires running an apt update and apt install sudo at launch:
docker run --rm -it -v /foo:/bar ubuntu:20.04 sh -c "useradd -m -s /bin/bash $USER && usermod -a -G sudo $USER && apt update && apt install sudo && passwd -d $USER && su - $USER"
Not ideal! I'd have hoped there was a much more simple way of doing this (using command-line options, not creating a new image), but I haven't found one.
1) Make sure that the user 999 has right privilege on the current directory, you need to try something like this in your docker file
FROM
RUN mkdir /home/999-user-dir && \
chown -R 999:998 /home/999-user-dir
WORKDIR /home/999-user-dir
USER 999
try to spin up the container using this image without the user argument and see if that works.
2) other reason could be permission issue on the below files, make sure your group 998 has read permission on these files
-rw-r--r-- 1 root root 690 Jan 2 06:27 /etc/passwd
-rw-r--r-- 1 root root 372 Jan 2 06:27 /etc/group
Thanks
So, on your host you probably see your user and group:
$ cat /etc/passwd
sdp:x:999:998::...
But inside the container, you will not see them in /etc/passwd.
This is the expected behavior, the host and the container are completely separated as long as you don't mount the /etc/passwd file inside the container (and you shouldn't do it from security perspective).
Now if you specified a default user inside your Dockerfile, the --user operator overrides the USER instruction, so you left without a username inside your container, but please notice that specifying the uid:gid option means that the container have the permissions of the user with the same uid value in the host.
Now for your request not to specify a user in the Dockerfile - that shouldn't be a problem. You can set it on runtime like you did as long as that uid matches an existing user uid on the host.
If you have to run some of the containers in privileged mode - please consider using user namespace.

how to create a docker image/container with same file rights as host user

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.

Changing the user's uid in a pre-build docker container (jenkins)

I am new to docker, so if this is a fairly obvious process that I am missing, I do apologize for the dumb question up front.
I am setting up a continuous integration server using the jenkins docker image. I did a docker pull jenkins, and created a user jenkins to allow me to mount the /var/jenkins_home in the container to my host's /var/jenkins_home (also owned by jenkins:jenkins user).
the problem is that the container seems to define the jenkins user with uid 102, but my host has the jenkins user as 1002, so when I run it I get:
docker run --name jenkins -u jenkins -p 8080 -v /var/jenkins_home:/var/jenkins_home jenkins
/usr/local/bin/jenkins.sh: line 25: /var/jenkins_home/copy_reference_file.log: Permission denied
I would simply make the uid for the host's jenkins user be 102 in /etc/passwd, but that uid is already taken by sshd. I think the solution is to change the container to use uid 1002 instead, but I am not sure how.
Edit
Actually, user 102 on the host is messagebus, not sshd.
Please take a look at the docker file I just uploaded:
https://github.com/bdruemen/jenkins-docker-uid-from-volume/blob/master/Dockerfile .
Here the UID is extracted from a mounted volume (host directory), with
stat -c '%u' <VOLUME-PATH>
Then the UID of the container user is changed to the same value with
usermod -u <UID>
This has to be done as root, but then root privileges are dropped with
gosu <USERNAME> <COMMAND>
Everything is done in the ENTRYPOINT, so the real UID is unknown until you run
docker run -d -v <HOST-DIRECTORY>:<VOLUME-PATH> ...
Note that after changing the UID, there might be some other files no longer accessible for the process in the container, so you might need a
chown -R <USERNAME> <SOME-PATH>
before the gosu command.
You can also change the GID, see my answer here
Jenkins in docker with access to host docker
and maybe you want to change both to increase security.
You can simply change the UID in /etc/passwd, assuming that no other user has UID 1002.
You will then need to change the ownership of /var/jenkins_home on your host to UID 1002:
chown -R jenkins /var/jenkins_home
In fact, you don't even need a jenkins user on the host to do this; you can simply run:
chown -R 1002 /var/jenkins_home
This will work even if there is no user with UID 1002 available locally.
Another solution is to build your own docker image, based on the Jenkins image, that has an ENTRYPOINT script that looks something like:
#!/bin/sh
chown -R jenkins /var/jenkins_home
exec "$#"
This will (recursively) chown /var/jenkins_home inside the container to whatever UID is used by the jenkins user (this assumes that your Docker contains is starting as root, which is true unless there was a USER directive in the history of the image).
Update
You can create a new image, based on (FROM ...) the jenkins image, with a Dockerfile that performs the necessary edits to the /etc/passwd file. But that seems a lot of work for not much gain. It's not clear why you're creating jenkins user on the host or if you actually need access to the jenkins home directory on the host.
If all you're doing is providing data persistence, consider using a data volume container and --volumes-from rather than a host volume, because this will isolate the data volume from your host so that UID conflicts don't cause confusion.
I had the same error, I turned SELinux off (on CEntOS) and it works.
Otherwise, it woukd be better to tune SElinux with SEManage commands.
The ideal is to change the user UID in your Dockerfile used by jenkins with the same UID used by the Host (remember that it must be done for non-root users, if root create a new user and configure the service inside the container to that user).
Assuming the user's UID on the host is 1003 and the user is called jenkins (use $id to get the user and group id).
Add to your Dockerfile
# Modifies the user's UID and GID
RUN groupmod -g 1003 jenkins && usermod -u 1003 -g 1003 jenkins
# I use a group (docker) on my host to organize the privileges,
#if that's your # case add the user to this group inside the container.
RUN groupadd -g 998 docker && usermod -aG docker nginx

Resources