Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Better support for rootless containers (#636)
# Short summary Running containers under rootless docker/podman lets us: - avoid permission issues in mounted volumes without any extra run setting (root in container == user at the host) - avoid granting privileges to host users, enhancing system security - simplify container images (just run them with root, it's safe because it's actually the user without extra privileges!) For backwards compatibility, this pull request assumes users still don't run rootless docker by default, so we require users running rootless containers to define `-e RUNROOTLESS=true`. Until a method is devised to detect if a container is running rootless or not from within the container. - https://docs.docker.com/engine/security/rootless/ - https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md In this pull request, I modify the userconf script so if we are running rootless `-e RUNROOTLESS=true` we do not create users or change sudoers or do any of those things. With this change, we can run rstudio server under an unprivileged user without issues. ``` mkdir $HOME/work podman run -ti -e PASSWORD=helloworld -e RUNROOTLESS=true -v $HOME/work:/root/work -p 10000:8787 <img-name> ``` Visit localhost:10000 and login using root and helloworld. Thanks for considering merging this. ## Alternative As an alternative solution we could have different images: Something like `rocker/rstudio-rootless` or `rocker-rootless/rstudio` ``` FROM rocker/rstudio COPY init_userconf.sh /etc/cont-init.d/02_userconf ``` But I think it is easier to just merge this # Historic background Here is some longer story of why things are the way they are, to bring some context to all this user mess that exists in docker images. It may not be fully accurate, but good enough ### Stage 1: We are root - Docker is a client-server application. - The docker daemon runs as the root user. - docker users need to run docker commands under root or using `sudo` (e.g. `sudo docker run...`). This situation is problematic because: - docker users have root access to the host - docker images may create/modify files as root in the host (even accidentally if some host directory is mounted!) - files created by docker are owned by the root user, this easily becomes a permission mess - There are audit limitations, since all docker users connect to the same docker daemon typically without authentication. We don't know who does what. People wants to use docker so much that a `docker` user group is created, and all users in that `docker` group do not need to type `sudo` to run docker anymore, although effectively it is as if they did. The risk is still there, only hidden. ### Stage 2: Images drop root privileges Docker is a complex piece of software that relies on namespaces and control groups, features of the linux kernel that are under heavy development. Therefore the fastest and easiest solution to address some of these issues comes from the image builders. The container starts running as root, but image builders following best-practices drop those permissions as soon as possible and read environment variables set when the container is created so users can choose the user id they would like to be used to create files, avoiding the file permission mess. This depends on the good-will of the image builder, but "works". This adds a lot of complexity, because images now have to consider multiple users and permissions (allow root inside the container for apt-get install, use sudoers...) ### Stage 3: Run with `docker run --user` Docker allows to specify the user the image will run as. The Docker daemon runs as root, but the container is started as running with a user id, and that user in the container typically does not have root privileges anymore. Docker here avoids file permission issues, but at some cost. Since now the image starting scripts do not have root access in the container, allowing for apt-get inside the container becomes far more tricky. To my knowledge, this option does not get a lot of adoption in rocker and jupyter notebook images. ### Stage 4: User namespaces `docker run --user-ns` The linux kernel starts having support for user namespaces. Basically we can map user ids in the container to a range of user ids in the host. Depending on how this is used, this can lead to files created by the image not being owned by root anymore, but by a super high user ID. To my knowledge, this option does not get a lot of adoption in rocker and jupyter notebooks. ### Stage 5: Rootless docker Enough namespace and cgroup solutions exist in the kernel for the docker daemon to be able to run containers without root permissions. Running the docker daemon as root is a security risk, and it is also an auditability issue, so this becomes an actual better-designed solution to the permission problem. Now each user can run its own docker daemon. Since the docker daemon runs as the user, it does not have any special permissions, no damage to the host can be made. Therefore we do not need images to drop privileges or follow any best practices to be responsible, since they are not allowed to do any damage by default anymore. Things can be simple again! However we must still be backwards compatible with those who have or want to use docker as root. Hi **podman**! Podman was designed to run rootless by default, and maybe even was able to do so before docker (I don't really know). podman does not even need a daemon, although podman supports a daemon to increase compatibility with docker. How does this work? Using user namespaces in a more transparent way: - alice, with user id 1000, has a rootless docker daemon running, so she can run docker daemons without any root privilege. - alice creates a container (podman run..., docker run...), mounting some directories she has access to (`--volume`) without caring for permissions or user ids - docker/podman creates the container and runs the entrypoint. The entrypoint seems to run as `root` from within the container, but from the host it appears to run as `alice`. If the entrypoint scripts do not do anything weird with the users, the entrypoint and commands that run afterwards run as well as root/alice. Files created on the mounted volume appear to be owned by alice. If alice tries to mount a volume she can't write on (e.g. --volume /sbin:/somewhere) the root/alice user in the container won't be able to write on it, because alice does not have permissions to do that. That's all we want and need! But... backwards compatiblity! Until someone finds a convention to determine if a container is running under rootless docker, we, image builders, can't tell if we should drop privileges or simply use them. So, I would like to ask alice to set an environment variable to tell me if she is running rootless, so I can just use the root/alice user without a care for setting up users and permissions and sudoers. I'll have to ask alice to use an environment variable for now...
- Loading branch information