diff --git a/Dockerfile b/Dockerfile index 9d6b96fbcc0..1b36d0d91fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN apt-get update && apt-get install -y \ protobuf-c-compiler \ protobuf-compiler \ python-minimal \ + uidmap \ --no-install-recommends \ && apt-get clean diff --git a/tests/integration/exec.bats b/tests/integration/exec.bats index 70730de8811..8d943aab7f4 100644 --- a/tests/integration/exec.bats +++ b/tests/integration/exec.bats @@ -100,8 +100,8 @@ function teardown() { } @test "runc exec --user" { - # --user can't work in rootless containers - requires root + # --user can't work in rootless containers that don't have idmap. + [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap # run busybox detached runc run -d --console-socket $CONSOLE_SOCKET test_busybox @@ -110,5 +110,5 @@ function teardown() { runc exec --user 1000:1000 test_busybox id [ "$status" -eq 0 ] - [[ ${output} == "uid=1000 gid=1000" ]] + [[ "${output}" == "uid=1000 gid=1000"* ]] } diff --git a/tests/integration/helpers.bash b/tests/integration/helpers.bash index 29e0fef68c5..e44fcaa7e17 100644 --- a/tests/integration/helpers.bash +++ b/tests/integration/helpers.bash @@ -58,15 +58,41 @@ function __runc() { "$RUNC" --log /proc/self/fd/2 --root "$ROOT" "$@" } -# Wrapper for runc spec. +# Wrapper for runc spec, which takes only one argument (the bundle path). function runc_spec() { - local args="" + ! [[ "$#" > 1 ]] + + local args=() + local bundle="" if [ "$ROOTLESS" -ne 0 ]; then - args+="--rootless" + args+=("--rootless") + fi + if [ "$#" -ne 0 ]; then + bundle="$1" + args+=("--bundle" "$bundle") fi - runc spec $args "$@" + runc spec "${args[@]}" + + # Always add additional mappings if we have idmaps. + if [[ "$ROOTLESS" -ne 0 ]] && [[ "$ROOTLESS_FEATURES" == *"idmap"* ]]; then + runc_rootless_idmap "$bundle" + fi +} + +# Shortcut to add additional uids and gids, based on the values set as part of +# a rootless configuration. +function runc_rootless_idmap() { + bundle="${1:-.}" + cat "$bundle/config.json" \ + | jq '.mounts |= map((select(.type == "devpts") | .options += ["gid=5"]) // .)' \ + | jq '.linux.uidMappings |= .+ [{"hostID": '"$ROOTLESS_UIDMAP_START"', "containerID": 1000, "size": '"$ROOTLESS_UIDMAP_LENGTH"'}]' \ + | jq '.linux.gidMappings |= .+ [{"hostID": '"$ROOTLESS_GIDMAP_START"', "containerID": 100, "size": 1}]' \ + | jq '.linux.gidMappings |= .+ [{"hostID": '"$(($ROOTLESS_GIDMAP_START+10))"', "containerID": 1, "size": 20}]' \ + | jq '.linux.gidMappings |= .+ [{"hostID": '"$(($ROOTLESS_GIDMAP_START+100))"', "containerID": 1000, "size": '"$(($ROOTLESS_GIDMAP_LENGTH-1000))"'}]' \ + >"$bundle/config.json.tmp" + mv "$bundle/config.json"{.tmp,} } # Fails the current test, providing the error given. @@ -90,14 +116,19 @@ function requires() { skip "test requires ${var}" fi ;; + rootless_idmap) + if [[ "$ROOTLESS_FEATURES" != *"idmap"* ]]; then + skip "test requires ${var}" + fi + ;; cgroups_kmem) if [ ! -e "$KMEM" ]; then - skip "Test requires ${var}." + skip "Test requires ${var}" fi ;; cgroups_rt) if [ ! -e "$RT_PERIOD" ]; then - skip "Test requires ${var}." + skip "Test requires ${var}" fi ;; *) diff --git a/tests/integration/spec.bats b/tests/integration/spec.bats index 60617060624..c9ba2aaaceb 100644 --- a/tests/integration/spec.bats +++ b/tests/integration/spec.bats @@ -51,7 +51,7 @@ function teardown() { [ ! -e "$HELLO_BUNDLE"/config.json ] # test generation of spec does not return an error - runc_spec --bundle "$HELLO_BUNDLE" + runc_spec "$HELLO_BUNDLE" [ "$status" -eq 0 ] # test generation of spec created our config.json (spec) diff --git a/tests/integration/start_detached.bats b/tests/integration/start_detached.bats index 0dc5e0d8733..7f177b86594 100644 --- a/tests/integration/start_detached.bats +++ b/tests/integration/start_detached.bats @@ -21,8 +21,8 @@ function teardown() { } @test "runc run detached ({u,g}id != 0)" { - # cannot start containers as another user in rootless setup - requires root + # cannot start containers as another user in rootless setup without idmap + [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap # replace "uid": 0 with "uid": 1000 # and do a similar thing for gid. diff --git a/tests/integration/start_hello.bats b/tests/integration/start_hello.bats index 2e935728085..a706be27d0a 100644 --- a/tests/integration/start_hello.bats +++ b/tests/integration/start_hello.bats @@ -21,8 +21,8 @@ function teardown() { } @test "runc run ({u,g}id != 0)" { - # cannot start containers as another user in rootless setup - requires root + # cannot start containers as another user in rootless setup without idmap + [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap # replace "uid": 0 with "uid": 1000 # and do a similar thing for gid. diff --git a/tests/integration/tty.bats b/tests/integration/tty.bats index 985a9b757b2..a59652bba70 100644 --- a/tests/integration/tty.bats +++ b/tests/integration/tty.bats @@ -24,9 +24,9 @@ function teardown() { } @test "runc run [tty owner]" { - # tty chmod is not doable in rootless containers. + # tty chmod is not doable in rootless containers without idmap. # TODO: this can be made as a change to the gid test. - requires root + [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap # Replace sh script with stat. sed -i 's/"sh"/"sh", "-c", "stat -c %u:%g $(tty) | tr : \\\\\\\\n"/' config.json @@ -40,8 +40,8 @@ function teardown() { } @test "runc run [tty owner] ({u,g}id != 0)" { - # tty chmod is not doable in rootless containers. - requires root + # tty chmod is not doable in rootless containers without idmap. + [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap # replace "uid": 0 with "uid": 1000 # and do a similar thing for gid. @@ -76,9 +76,9 @@ function teardown() { } @test "runc exec [tty owner]" { - # tty chmod is not doable in rootless containers. + # tty chmod is not doable in rootless containers without idmap. # TODO: this can be made as a change to the gid test. - requires root + [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap # run busybox detached runc run -d --console-socket $CONSOLE_SOCKET test_busybox @@ -95,8 +95,8 @@ function teardown() { } @test "runc exec [tty owner] ({u,g}id != 0)" { - # tty chmod is not doable in rootless containers. - requires root + # tty chmod is not doable in rootless containers without idmap. + [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap # replace "uid": 0 with "uid": 1000 # and do a similar thing for gid. diff --git a/tests/rootless.sh b/tests/rootless.sh index 2613c7022c2..cb2c0647997 100755 --- a/tests/rootless.sh +++ b/tests/rootless.sh @@ -19,9 +19,42 @@ # a new feature, please match the existing style. Add an entry to $ALL_FEATURES, # and add an enable_* and disable_* hook. -ALL_FEATURES=() +ALL_FEATURES=("idmap") ROOT="$(readlink -f "$(dirname "${BASH_SOURCE}")/..")" +# FEATURE: Opportunistic new{uid,gid}map support, allowing a rootless container +# to be set up with the usage of helper setuid binaries. + +function enable_idmap() { + export ROOTLESS_UIDMAP_START=100000 ROOTLESS_UIDMAP_LENGTH=65536 + export ROOTLESS_GIDMAP_START=200000 ROOTLESS_GIDMAP_LENGTH=65536 + + # Set up sub{uid,gid} mappings. + [ -e /etc/subuid.tmp ] && mv /etc/subuid{.tmp,} + ( grep -v '^rootless' /etc/subuid ; echo "rootless:$ROOTLESS_UIDMAP_START:$ROOTLESS_UIDMAP_LENGTH" ) > /etc/subuid.tmp + mv /etc/subuid{.tmp,} + [ -e /etc/subgid.tmp ] && mv /etc/subgid{.tmp,} + ( grep -v '^rootless' /etc/subgid ; echo "rootless:$ROOTLESS_GIDMAP_START:$ROOTLESS_GIDMAP_LENGTH" ) > /etc/subgid.tmp + mv /etc/subgid{.tmp,} + + # Reactivate new{uid,gid}map helpers if applicable. + [ -e /usr/bin/unused-newuidmap ] && mv /usr/bin/{unused-,}newuidmap + [ -e /usr/bin/unused-newgidmap ] && mv /usr/bin/{unused-,}newgidmap +} + +function disable_idmap() { + export ROOTLESS_UIDMAP_START ROOTLESS_UIDMAP_LENGTH + export ROOTLESS_GIDMAP_START ROOTLESS_GIDMAP_LENGTH + + # Deactivate sub{uid,gid} mappings. + [ -e /etc/subuid ] && mv /etc/subuid{,.tmp} + [ -e /etc/subgid ] && mv /etc/subgid{,.tmp} + + # Deactivate new{uid,gid}map helpers. setuid is preserved with mv(1). + [ -e /usr/bin/newuidmap ] && mv /usr/bin/{,unused-}newuidmap + [ -e /usr/bin/newgidmap ] && mv /usr/bin/{,unused-}newgidmap +} + # Create a powerset of $ALL_FEATURES (the set of all subsets of $ALL_FEATURES). # We test all of the possible combinations (as long as we don't add too many # feature knobs this shouldn't take too long -- but the number of tested