Skip to content
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

Fix using rocker for non-root containers #50

Merged
merged 9 commits into from
Nov 4, 2020

Conversation

meyerj
Copy link
Contributor

@meyerj meyerj commented Aug 22, 2019

If the base image given by the user does not have root privileges, building the rockerized image will fail. Extensions seem to expect that their Dockerfile snippets are executed with root privileges.

This patch injects a USER root instruction right after FROM base_image. It is a no-op if the base image already runs as the root user.

For similar reasons the user extension has to run last, because its snippet changes the active user again and subsequent snippets from other extensions might fail. But this is a special case only. Maybe the extension API should have a more general way to declare dependencies of extensions. For example, nvidia depends on x11. Without specifying both, I cannot run 3D-accelerated GUI applications in the rockerized container.

If the base image given by the user does not have root privileges,
building the rockerized image will fail. Extensions seem to expect that
their Dockerfile snippets are executed with root privileges.

This patch injects a `USER root` instruction right after FROM
base_image.

For similar reasons the `user` extension has to run last, because its
snippet changes the active user again and subsequent snippets from other
extensions might fail.
@tfoote
Copy link
Collaborator

tfoote commented Aug 28, 2019

Yeah, the user already has special logic to execute at the end I think that it will always be special. And likely each snippet could do something like declare if it should be user or root and then we'd set and unset the user ID in the dockerfile. See #10

A generic way to declare dependencies would also be valuable to get both ordering and automatically inject prerequisies like --x11. I'd also like to try to support an argument like --gui which would then be able to trigger the appropriate nvidia, amd, or intel graphics card plugin based on detectors for the hardware available.

- A user with the same UID already exists in the container image.
- A group with the same GID already exists in the container image.
- A user with the same name already exists in the container image.

In all those cases the existing user or group gets deleted before
creating a new one.

Changing the password and adding the user to the sudo group is redundant
to creating /etc/sudoers.d/rocker.
@meyerj
Copy link
Contributor Author

meyerj commented Sep 6, 2019

Yeah, the user already has special logic to execute at the end I think that it will always be special.

I removed the redundant reordering of extensions in 9df8a8c.

We also found that not all special cases were covered (054cbc4)

  • A user with the same UID already exists in the container image.
  • A group with the same GID already exists in the container image.
  • A user with the same name already exists in the container image.

In all those cases the existing user or group gets deleted now before creating a new one.

Changing the password and adding the user to the sudo group is not necessary with /etc/sudoers.d/rocker. Some usernames might not satisfy the password complexity checks done inside the container depending on the container OS and its configuration.

@codecov
Copy link

codecov bot commented Sep 11, 2019

Codecov Report

Merging #50 into master will increase coverage by 0.02%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #50      +/-   ##
==========================================
+ Coverage   86.29%   86.31%   +0.02%     
==========================================
  Files           7        7              
  Lines         591      592       +1     
==========================================
+ Hits          510      511       +1     
  Misses         81       81              
Impacted Files Coverage Δ
src/rocker/cli.py 0.00% <ø> (ø)
src/rocker/core.py 90.61% <100.00%> (+0.04%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ce7b13e...8cee014. Read the comment docs.

@meyerj meyerj requested a review from tfoote as a code owner October 20, 2020 19:32
…medir_helper

The tool fails with exit code 6 if the top-level directory does not exist yet.

    (a39bc32231c8)root@c71fe951b480:/# getent passwd johannes
    johannes:x:1000006:1000:Johannes Meyer:/share/homes/johannes:/bin/bash
    (a39bc32231c8)root@a39bc32231c8:/# rm share -R
    (a39bc32231c8)root@a39bc32231c8:/# mkhomedir_helper johannes; echo $?
    6
    (a39bc32231c8)root@a39bc32231c8:/# mkdir share
    (a39bc32231c8)root@a39bc32231c8:/# mkhomedir_helper johannes; echo $?
    0
Copy link
Collaborator

@tfoote tfoote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for extending the support to more of the corner cases.

Do you have public example images that could be used as test cases for some of these corner cases?

@tfoote tfoote merged commit 3585298 into osrf:master Nov 4, 2020
@meyerj meyerj deleted the fix-non-root-containers branch November 4, 2020 18:58
@meyerj
Copy link
Contributor Author

meyerj commented Nov 4, 2020

Thanks for extending the support to more of the corner cases.

Do you have public example images that could be used as test cases for some of these corner cases?

I do not have one that I can share as-is, and also did not find a public example on Docker Hub. But I created a simple Dockerfile based on ubuntu:latest and pushed it as meyerj/unprivileged:

FROM ubuntu
RUN useradd docker
USER docker
CMD ["bash"]

Without this patch:

$ rocker --user meyerj/unprivileged
[...]
Building docker file with arguments:  {'path': '/tmp/tmpjoilklgz', 'rm': True, 'nocache': False}
building > Step 1/5 : FROM meyerj/unprivileged
building >  ---> 22d887b3ed36
building > Step 2/5 : RUN if ! command -v sudo >/dev/null; then       apt-get update       && apt-get install -y sudo       && apt-get clean;     fi
building >  ---> Running in c9430b165011
building > Reading package lists...
building > E: List directory /var/lib/apt/lists/partial is missing. - Acquire (13: Permission denied)

no more output and success not detected
Build failed exiting

With this patch:

$ rocker --user meyerj/unprivileged
[...]
building > Step 1/6 : FROM meyerj/unprivileged
building >  ---> 22d887b3ed36
building > Step 2/6 : USER root
building >  ---> Using cache
building >  ---> 5a200b7615ba
building > Step 3/6 : RUN if ! command -v sudo >/dev/null; then       apt-get update       && apt-get install -y sudo       && apt-get clean;     fi
building >  ---> Using cache
building >  ---> 3e286e07cd50
building > Step 4/6 : RUN existing_user_by_uid=`getent passwd "1000006" | cut -f1 -d: || true` &&     if [ -n "${existing_user_by_uid}" ]; then userdel -r "${existing_user_by_uid}"; fi &&     existing_user_by_name=`getent passwd "johannes" | cut -f1 -d: || true` &&     if [ -n "${existing_user_by_name}" ]; then userdel -r "${existing_user_by_name}"; fi &&     existing_group_by_gid=`getent group "1000" | cut -f1 -d: || true` &&     if [ -z "${existing_group_by_gid}" ]; then       groupadd -g "1000" "johannes";     fi &&     useradd --no-log-init --no-create-home --uid "1000006" -s "/bin/bash" -c "Johannes Meyer,,," -g "1000" -d "/home/johannes" "johannes" &&     echo "johannes ALL=NOPASSWD: ALL" >> /etc/sudoers.d/rocker
building >  ---> Using cache
building >  ---> 0e80cb597379
building > Step 5/6 : RUN mkdir -p "$(dirname "/home/johannes")" && mkhomedir_helper johannes
building >  ---> Using cache
building >  ---> 03cfd1c13f33
building > Step 6/6 : USER johannes
building >  ---> Using cache
building >  ---> 17794f1d1625
building > Successfully built 17794f1d1625
Executing command: 
docker run -it   --rm      17794f1d1625 
johannes@3a3b8efd5ac5:/$ 

Even without --user some extensions expect that their snippets run with root privileges, e.g. --nvidia:

$ rocker --nvidia meyerj/unprivileged
[...]
running,  docker run -it --rm f31d2d29af59
output:  standard_init_linux.go:211: exec user process caused "permission denied"

/tmp/detect_os failed:
> standard_init_linux.go:211: exec user process caused "permission denied"
WARNING unable to detect os for base image 'meyerj/unprivileged', maybe the base image does not exist

But I also noticed a downside, namely if USER root is not actually required:

$ rocker meyerj/unprivileged
Building docker file with arguments:  {'path': '/tmp/tmpgppwbqbt', 'rm': True, 'nocache': False, 'pull': False}
building > Step 1/2 : FROM meyerj/unprivileged
building >  ---> 22d887b3ed36
building > Step 2/2 : USER root
building >  ---> Using cache
building >  ---> 5a200b7615ba
building > Successfully built 5a200b7615ba
Executing command: 
docker run -it   --rm      5a200b7615ba 
root@a3fde251aeda:/# 

So I guess a potential follow-up could be to extend class RockerExtension with a method that lets extensions specify whether they require root privileges or not, such that USER root is injected only if it is required by at least one active extension? Or each of those extension could add USER root individually in the snippet returned by get_snippet(), with the disadvantage that it might be added more than once.

130s pushed a commit to plusone-robotics/rocker that referenced this pull request Apr 17, 2021
* Fix using rocker for non-root containers

If the base image given by the user does not have root privileges,
building the rockerized image will fail. Extensions seem to expect that
their Dockerfile snippets are executed with root privileges.

This patch injects a `USER root` instruction right after FROM
base_image.

For similar reasons the `user` extension has to run last, because its
snippet changes the active user again and subsequent snippets from other
extensions might fail.

* Revert redundant reordering of extensions

* Fix user extension for some special cases

- A user with the same UID already exists in the container image.
- A group with the same GID already exists in the container image.
- A user with the same name already exists in the container image.

In all those cases the existing user or group gets deleted before
creating a new one.

Changing the password and adding the user to the sudo group is redundant
to creating /etc/sudoers.d/rocker.

* Suppress mail spool/home directory not found error output of userdel

* tests: Remove obsolete check in UserExtensionTest

* Revert "Suppress mail spool/home directory not found error output of userdel"

This reverts commit c520a4e.

* Create the parent directory of the home directory before calling mkhomedir_helper

The tool fails with exit code 6 if the top-level directory does not exist yet.

    (a39bc32231c8)root@c71fe951b480:/# getent passwd johannes
    johannes:x:1000006:1000:Johannes Meyer:/share/homes/johannes:/bin/bash
    (a39bc32231c8)root@a39bc32231c8:/# rm share -R
    (a39bc32231c8)root@a39bc32231c8:/# mkhomedir_helper johannes; echo $?
    6
    (a39bc32231c8)root@a39bc32231c8:/# mkdir share
    (a39bc32231c8)root@a39bc32231c8:/# mkhomedir_helper johannes; echo $?
    0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants