-
Notifications
You must be signed in to change notification settings - Fork 48
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
mambauser cannot write to bind mounts on Linux with Docker in rootless-mode #407
Comments
I also tried building the Docker image locally with the host user UID, GID and username included in the build process as # Build micromamba-docker:1.5.6 locally
docker build . -t mycromamba --build-arg="MAMBA_USER=$USER" \
--build-arg="MAMBA_USER_ID=$(id -u)" \
--build-arg="MAMBA_USER_GID=$(id -g)"
$ docker run --rm -it --user $UID:$GID -v "$(pwd):/home/foobar/data" mycromamba /bin/bash
$ cd /home/foobar/data/
$ ls -la
total 8
drwxr-xr-x 2 root root 4096 Jan 11 03:15 .
drwxrwxrwx 3 foobar foobar 4096 Jan 11 03:38 ..
# Writing to bind mount /home/foobar/data fails:
$ touch test.txt
touch: cannot touch 'test.txt': Permission denied
# Writing to user directory /home/foobar *inside the container* works (but is not bound to host)
$ cd ..
$ touch test.txt
$ ls
data test.txt |
Works for me: $ docker run -it --rm -v $(pwd):/home/mambauser -u $(id -u):$(id -g) mambaorg/micromamba:1.5.6 /bin/bash -c 'touch /home/mambauser/foobar'
$ docker version
Client: Docker Engine - Community
Version: 24.0.6
API version: 1.43
Go version: go1.20.7
Git commit: ed223bc
Built: Mon Sep 4 12:32:16 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 24.0.6
API version: 1.43 (minimum version 1.12)
Go version: go1.20.7
Git commit: 1a79695
Built: Mon Sep 4 12:32:16 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.24
GitCommit: 61f9fd88f79f081d64d6fa3bb1a0dc71ec870523
runc:
Version: 1.1.9
GitCommit: v1.1.9-0-gccaecfc
docker-init:
Version: 0.19.0
GitCommit: de40ad0
$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/" On the host, do you have write permission to the working directory where you are executing the this:
seems to indicate that you do not have write permissions in that directory on the host. |
Just upgraded docker to the same version you tried, and I still cannot reproduce it when in a host directory where I have write access. $ docker version
Client: Docker Engine - Community
Version: 24.0.7
API version: 1.43
Go version: go1.20.10
Git commit: afdd53b
Built: Thu Oct 26 09:08:17 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 24.0.7
API version: 1.43 (minimum version 1.12)
Go version: go1.20.10
Git commit: 311b9ff
Built: Thu Oct 26 09:08:17 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.26
GitCommit: 3dd1e886e55dd695541fdcd67420c2888645a495
runc:
Version: 1.1.10
GitCommit: v1.1.10-0-g18a0cb0
docker-init:
Version: 0.19.0
GitCommit: de40ad0 |
Works fine for me too: $ docker run --rm -it -v "$(pwd):/tmp" --user $UID:$GID mambaorg/micromamba:1.5.6 /bin/bash
(base) I have no name!@9b2eefcffcd4:/tmp$ touch test && ls
test |
Thanks for looking into this! For my py4docker project, I was able to solve the issue as follows:
Without a lot of effort, I cannot tell exactly if the UID of 1000 or the sudo group membership of the user on the host cause this behavior, but the cause is most likely one of the two. The CLI context is a requirement to access the Docker daemon, but does not fix the issue (I can still reproduce it by using the UID 1000 user with sudo rights). Again, thanks for your time and support! PS: The problem seems to have a long history, hence I add a link to resources and findings on the topic of Docker bind mounts here for future visitors to this issue. |
In this test, it is IMO not 100% certain if the write operation takes place inside the mount point or elsewhere (it depends on the WORKDIR). Edit: I checked in interactive mode; this indeed works if my user has no sudo membership nor a UID of 1000. |
One small addendum:
With
With
|
I also find that the (Note that micromamba 1.5.0 and later use UID/GID 57439 instead of 1000, so you need to modify that to |
For others running into similar problems, this tool might be useful: |
Still working on the issue... and I think there is an underlying problem that surfaces when you are using
As far as I understand, the non-root Docker in rootless mode utilizes See how # Create a test folder and file
# Default permissions set by umask
$ mkdir foo
$ cd foo
$ mkdir bar
$ touch mytext.txt
# Show permissions from the host user's perspective
$ ls -la
total 12
drwxr-xr-x 3 myusername myusername 4096 Apr 30 19:23 .
drwxr-xr-x 15 myusername myusername 4096 Apr 30 19:23 ..
drwxr-xr-x 2 myusername myusername 4096 Apr 30 19:23 bar
-rw-r--r-- 1 myusername myusername 0 Apr 30 19:23 mytext.txt
$ id
uid=1000(myusername) gid=1000(myusername) groups=1000(myusername),996(docker)
# Now turning on RootlessKit, as used by Docker
# RootlessKit creates user_namespaces and executes newuidmap/newgidmap along with subuid and subgid
# https://github.com/rootless-containers/rootlesskit
$ rootlesskit bash
root@110:~/foo# ls -la
total 12
drwxr-xr-x 3 root root 4096 Apr 30 19:23 .
drwxr-xr-x 15 root root 4096 Apr 30 19:23 ..
drwxr-xr-x 2 root root 4096 Apr 30 19:23 bar
-rw-r--r-- 1 root root 0 Apr 30 19:23 mytext.txt
root@110:~/foo# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
root@110:~/foo# exit You can see that This means that the non-root The exact mapping is determined by I am reopening the issue, because
It may be possible to fix this by
Any ideas will be very much appreciated! PS: The test from earlier on fails now, too, since I changed my system to a rootless Docker configuration: $ docker run --rm -it -v "$(pwd):/tmp" --user $(id -u):$(id -g) mambaorg/micromamba:1.5.8 /bin/bash
(base) I have no name!@a84cfa7e0ace:/tmp$ touch test
touch: cannot touch 'test': Permission denied
(base) I have no name!@a84cfa7e0ace:/tmp$ id
uid=1000 gid=1000 groups=1000
(base) I have no name!@a84cfa7e0ace:/tmp$ ls -la
total 12
drwxr-xr-x 3 root root 4096 Apr 30 19:23 .
drwxr-xr-x 17 root root 4096 Apr 30 19:54 ..
drwxr-xr-x 2 root root 4096 Apr 30 19:23 bar
-rw-r--r-- 1 root root 0 Apr 30 19:23 mytext.txt As shown above with # Permissions from the host user's perspective
$ ls -la
total 12
drwxr-xr-x 3 myusername myusername 4096 Apr 30 19:23 .
drwxr-xr-x 15 myusername myusername 4096 Apr 30 19:23 ..
drwxr-xr-x 2 myusername myusername 4096 Apr 30 19:23 bar
-rw-r--r-- 1 myusername myusername 0 Apr 30 19:23 mytext.txt
$ id
uid=1000(myusername) gid=1000(myusername) groups=1000(myusername),996(docker) |
Wow, thanks @mfhepp for your persistence in getting to the bottom of this issue! This looks like it was quite some effort. I have not yet used rootless mode myself (although I probably should), so this is a bit beyond my current comfort level. Thus I don't have a preference among your suggested approaches for a fix. Perhaps @wholtz has some thoughts? |
Have been doing additional experiments today - here is my current summary: Essentially, there are at least four solutions: Option 1: Make I think the most pragmatic way to deal with this is to use
Then This should work and is IMO quite safe. The user is not root on the host. A similar approach has been added to syzkaller. Downsides:
Credits:
In my py4docker project, I will likely add this to See also my answer in https://stackoverflow.com/a/78412890/516699. Option 2: Mimic I think I do now understand what happens when
Example:
This says that for the user
You can debug the mapping rules from container to host like so :
Possible Solution:
sudo groupadd -g 57439 mambauser_host
sudo useradd mambauser_host -u 57439 -g 57439 -m -s /bin/bash
sudo paddwd mambauser_host
This requires a bit of thinking ;-): We want the UID on the host to be 57439. The UID inside the container should be > 1000, e.g. 1002. In order to map 1002 in the container to 57439 on the host for the user
to sudo nano /etc/subuid
# Insert this line
mambauser_host:56437:1002
# Save and exit
sudo nano /etc/subgid
# Insert this line
mambauser_host:56437:1002
# Save and exit
# Restart system for the changes to take effect
# There might be a more elegant way Notes:
If we then run the container with docker run --rm -it -v "$(pwd):/tmp" --user 1002:1002 mambaorg/micromamba:1.5.8 /bin/bash the UIDs starting from 1 will be mapped to the UID range starting at 56437, like to
Et voilà! I have not yet tested this, but wanted to save the intermediate status. **This should basically do what Details and Credits: https://rootlesscontaine.rs/how-it-works/userns/ Option 3: Use Podman with # Note that we do not use the --user option, so mambauser remains 57439:57439!
podman run --rm -it -v "$(pwd):/tmp" --userns=keep-id:uid=57439,gid=57439 mambaorg/micromamba:1.5.8 /bin/bash This is untested as I have no Podman installation at hand, but likely the cleanest approach. Option 4: Disable user namespace with Docker in rootless mode In theory, it might be possible to use docker run --rm -it -v "$(pwd):/tmp" --userns=host mambaorg/micromamba:1.5.8 /bin/bash But it does not work, at least on my test server, and there are a bunch of Docker issues related to this, e.g. this one. It might also introduce new security issues. Hope you find this useful :-)! The problem has for long been a huge blocker in my workflow and I would have loved to avoid digging so deeply into this ;-). And of course, the underlying challenges are not specific to |
Addendum:
sudo usermod --add-subuids <start>-<end> <username>
|
Update:
|
Solution and Conclusion1. The best solution is to install podman run --rm -it -v "$(pwd):/tmp" --userns=keep-id --user $(id -u):$(id -g) docker.io/mambaorg/micromamba:1.5.8 /bin/bash In there, 2. The second-best solution is to use docker run --rm -it -v "$(pwd):/tmp" --user root mambaorg/micromamba:1.5.8 /bin/bash
The other options do not work; tweaking the Hope this is useful for many of you! Frankly, it was a nightmare to get to this solution and insight and I learned many things that are intellectually interesting but were not on my bucket-list to master ;-). Most important to say: TODO: Add summary and pointer to this to the documentation. |
@mfhepp thanks for the detailed investigation and clear recommendations. I am certainly in support of increasing our documentation in this area. Do you have any interest in putting together a documentation PR? |
Much appreciated! Will try to send a PR when I will have done my homework on py4docker! |
Just to add for the Podman case. I'm not exactly sure off the top of my head what the equivalent CLI flag looks like, but with systemd quadlet config you'd use this:
The I'm short on time, but since the feature is not always easy to grok from the Podman docs, or compare to the similar features Podman has for this, I'll copy/paste some older docs I wrote to help give readers more confidence in understanding this feature (_the #### Mapping ownership between container and host users
Podman supports a few different approaches for this functionality. For rootless Quadlets you will likely want to use `UIDMap` (_`GIDMap` will use this same mapping by default_).
- `UIDMap` + `GIDMap` works by mapping user and group IDs from a container, to IDs associated for a user on the host [configured in `/etc/subuid` + `/etc/subgid`][podman-docs::rootless-mode] (_this isn't necessary for rootful Podman_).
- Each mapping must be unique, thus only a single container UID can map to your rootless UID on the host. Every other container UID mapped must be within the configured range from `/etc/subuid`.
- Rootless containers have one additional level of mapping involved. This is an offset from their `/etc/subuid` entry starting from `0`, but can be inferred when the intended UID on the host is prefixed with `@`
!!! example
The most common case is to map the containers root user (UID `0`) to your host user ID.
For a rootless user with the UID `1000` on the host, any of the following `UIDMap` values are equivalent:
- **`UIDMap=+0:0`:** The 1st `0` is the container root ID and the 2nd `0` refers to host mapping ID. For rootless the mapping ID is an indirect offset to their user entry in `/etc/subuid` where `0` maps to their host user ID.
- **`UIDMap=+0:@1000`:** A rootless Quadlet can also use `@` as a prefix which tells Podman to infer the mapping ID from `/etc/subuid`.
- **`UIDMap=+0:@%U`:** Instead of providing the explicit rootless UID, a better approach is to leverage `%U` (_a [systemd specifier][systemd-docs::config-specifiers]_) which will resolve to the UID of your rootless user that starts the Quadlet service.
??? tip "What is the `+` syntax?"
Prefixing the container ID with `+` is a a podman feature similar to `@`, which ensures `/etc/subuid` is mapped fully.
For example `UIDMap=+5000:@%U` is the short-hand equivalent to:
```ini
UIDMap=5000:0:1
UIDMap=0:1:5000
UIDMap=5001:5001:60536
```
The third value is the amount of IDs to map from the `container:host` pair as an offset/range. It defaults to `1`.
In addition to our explicit `5000:0` mapping, the `+` ensures:
- That we have a mapping of all container ID prior to `5000` to IDs from our rootless user entry in `/etc/subuid` on the host.
- It also adds a mapping after this value for the remainder of the range configured in `/etc/subuid` which covers the `nobody` user in the container.
Within the container you can view these mappings via `cat /proc/self/uid_map`. |
If a script wants to write to the current working directory on the host system, an obvious way is to use a bind mount to map a directory on the host to a directory inside the container.
Unfortunately, this does not work with Docker on Linux systems; the non-root
mambauser
cannot write to directories from bind mounts, no matter if we set the UID/GID to that of the user on the host or not:Writing to Docker bind volumes on Linux systems as non-root users is a well-known and complicated topic, but I wonder if there is an elegant way of adding the
mambauser
to the group that has write access to a bind mount point.Or is there any other way of writing to a directory on the host from the
mambauser
?Note: The issue does not appear on Docker Desktop for OSX, as the built-in VM maps between the host system and the Docker environment.
References:
Addendum: Tested with Micromamba:1.5.6, Docker version 24.0.7, build afdd53b on Debian 11.8
Docker version 24.0.7, build afdd53b
The text was updated successfully, but these errors were encountered: